editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use collections::HashMap;
   17use futures::{StreamExt, channel::oneshot};
   18use gpui::{
   19    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   20    VisualTestContext, WindowBounds, WindowOptions, div,
   21};
   22use indoc::indoc;
   23use language::{
   24    BracketPairConfig,
   25    Capability::ReadWrite,
   26    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   27    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   28    language_settings::{
   29        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::Formatter;
   34use languages::rust_lang;
   35use lsp::CompletionParams;
   36use multi_buffer::{IndentGuide, PathKey};
   37use parking_lot::Mutex;
   38use pretty_assertions::{assert_eq, assert_ne};
   39use project::{
   40    FakeFs,
   41    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   42    project_settings::LspSettings,
   43};
   44use serde_json::{self, json};
   45use settings::{
   46    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   47    ProjectSettingsContent,
   48};
   49use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   50use std::{
   51    iter,
   52    sync::atomic::{self, AtomicUsize},
   53};
   54use test::build_editor_with_project;
   55use text::ToPoint as _;
   56use unindent::Unindent;
   57use util::{
   58    assert_set_eq, path,
   59    rel_path::rel_path,
   60    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   61    uri,
   62};
   63use workspace::{
   64    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   65    OpenOptions, ViewId,
   66    invalid_item_view::InvalidItemView,
   67    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   68    register_project_item,
   69};
   70
   71fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   72    editor
   73        .selections
   74        .display_ranges(&editor.display_snapshot(cx))
   75}
   76
   77#[gpui::test]
   78fn test_edit_events(cx: &mut TestAppContext) {
   79    init_test(cx, |_| {});
   80
   81    let buffer = cx.new(|cx| {
   82        let mut buffer = language::Buffer::local("123456", cx);
   83        buffer.set_group_interval(Duration::from_secs(1));
   84        buffer
   85    });
   86
   87    let events = Rc::new(RefCell::new(Vec::new()));
   88    let editor1 = cx.add_window({
   89        let events = events.clone();
   90        |window, cx| {
   91            let entity = cx.entity();
   92            cx.subscribe_in(
   93                &entity,
   94                window,
   95                move |_, _, event: &EditorEvent, _, _| match event {
   96                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   97                    EditorEvent::BufferEdited => {
   98                        events.borrow_mut().push(("editor1", "buffer edited"))
   99                    }
  100                    _ => {}
  101                },
  102            )
  103            .detach();
  104            Editor::for_buffer(buffer.clone(), None, window, cx)
  105        }
  106    });
  107
  108    let editor2 = cx.add_window({
  109        let events = events.clone();
  110        |window, cx| {
  111            cx.subscribe_in(
  112                &cx.entity(),
  113                window,
  114                move |_, _, event: &EditorEvent, _, _| match event {
  115                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  116                    EditorEvent::BufferEdited => {
  117                        events.borrow_mut().push(("editor2", "buffer edited"))
  118                    }
  119                    _ => {}
  120                },
  121            )
  122            .detach();
  123            Editor::for_buffer(buffer.clone(), None, window, cx)
  124        }
  125    });
  126
  127    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  128
  129    // Mutating editor 1 will emit an `Edited` event only for that editor.
  130    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  131    assert_eq!(
  132        mem::take(&mut *events.borrow_mut()),
  133        [
  134            ("editor1", "edited"),
  135            ("editor1", "buffer edited"),
  136            ("editor2", "buffer edited"),
  137        ]
  138    );
  139
  140    // Mutating editor 2 will emit an `Edited` event only for that editor.
  141    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  142    assert_eq!(
  143        mem::take(&mut *events.borrow_mut()),
  144        [
  145            ("editor2", "edited"),
  146            ("editor1", "buffer edited"),
  147            ("editor2", "buffer edited"),
  148        ]
  149    );
  150
  151    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  152    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  153    assert_eq!(
  154        mem::take(&mut *events.borrow_mut()),
  155        [
  156            ("editor1", "edited"),
  157            ("editor1", "buffer edited"),
  158            ("editor2", "buffer edited"),
  159        ]
  160    );
  161
  162    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  163    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  164    assert_eq!(
  165        mem::take(&mut *events.borrow_mut()),
  166        [
  167            ("editor1", "edited"),
  168            ("editor1", "buffer edited"),
  169            ("editor2", "buffer edited"),
  170        ]
  171    );
  172
  173    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  174    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  175    assert_eq!(
  176        mem::take(&mut *events.borrow_mut()),
  177        [
  178            ("editor2", "edited"),
  179            ("editor1", "buffer edited"),
  180            ("editor2", "buffer edited"),
  181        ]
  182    );
  183
  184    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  185    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  186    assert_eq!(
  187        mem::take(&mut *events.borrow_mut()),
  188        [
  189            ("editor2", "edited"),
  190            ("editor1", "buffer edited"),
  191            ("editor2", "buffer edited"),
  192        ]
  193    );
  194
  195    // No event is emitted when the mutation is a no-op.
  196    _ = editor2.update(cx, |editor, window, cx| {
  197        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  198            s.select_ranges([0..0])
  199        });
  200
  201        editor.backspace(&Backspace, window, cx);
  202    });
  203    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  204}
  205
  206#[gpui::test]
  207fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  208    init_test(cx, |_| {});
  209
  210    let mut now = Instant::now();
  211    let group_interval = Duration::from_millis(1);
  212    let buffer = cx.new(|cx| {
  213        let mut buf = language::Buffer::local("123456", cx);
  214        buf.set_group_interval(group_interval);
  215        buf
  216    });
  217    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  218    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  219
  220    _ = editor.update(cx, |editor, window, cx| {
  221        editor.start_transaction_at(now, window, cx);
  222        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  223            s.select_ranges([2..4])
  224        });
  225
  226        editor.insert("cd", window, cx);
  227        editor.end_transaction_at(now, cx);
  228        assert_eq!(editor.text(cx), "12cd56");
  229        assert_eq!(
  230            editor.selections.ranges(&editor.display_snapshot(cx)),
  231            vec![4..4]
  232        );
  233
  234        editor.start_transaction_at(now, window, cx);
  235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  236            s.select_ranges([4..5])
  237        });
  238        editor.insert("e", window, cx);
  239        editor.end_transaction_at(now, cx);
  240        assert_eq!(editor.text(cx), "12cde6");
  241        assert_eq!(
  242            editor.selections.ranges(&editor.display_snapshot(cx)),
  243            vec![5..5]
  244        );
  245
  246        now += group_interval + Duration::from_millis(1);
  247        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  248            s.select_ranges([2..2])
  249        });
  250
  251        // Simulate an edit in another editor
  252        buffer.update(cx, |buffer, cx| {
  253            buffer.start_transaction_at(now, cx);
  254            buffer.edit([(0..1, "a")], None, cx);
  255            buffer.edit([(1..1, "b")], None, cx);
  256            buffer.end_transaction_at(now, cx);
  257        });
  258
  259        assert_eq!(editor.text(cx), "ab2cde6");
  260        assert_eq!(
  261            editor.selections.ranges(&editor.display_snapshot(cx)),
  262            vec![3..3]
  263        );
  264
  265        // Last transaction happened past the group interval in a different editor.
  266        // Undo it individually and don't restore selections.
  267        editor.undo(&Undo, window, cx);
  268        assert_eq!(editor.text(cx), "12cde6");
  269        assert_eq!(
  270            editor.selections.ranges(&editor.display_snapshot(cx)),
  271            vec![2..2]
  272        );
  273
  274        // First two transactions happened within the group interval in this editor.
  275        // Undo them together and restore selections.
  276        editor.undo(&Undo, window, cx);
  277        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  278        assert_eq!(editor.text(cx), "123456");
  279        assert_eq!(
  280            editor.selections.ranges(&editor.display_snapshot(cx)),
  281            vec![0..0]
  282        );
  283
  284        // Redo the first two transactions together.
  285        editor.redo(&Redo, window, cx);
  286        assert_eq!(editor.text(cx), "12cde6");
  287        assert_eq!(
  288            editor.selections.ranges(&editor.display_snapshot(cx)),
  289            vec![5..5]
  290        );
  291
  292        // Redo the last transaction on its own.
  293        editor.redo(&Redo, window, cx);
  294        assert_eq!(editor.text(cx), "ab2cde6");
  295        assert_eq!(
  296            editor.selections.ranges(&editor.display_snapshot(cx)),
  297            vec![6..6]
  298        );
  299
  300        // Test empty transactions.
  301        editor.start_transaction_at(now, window, cx);
  302        editor.end_transaction_at(now, cx);
  303        editor.undo(&Undo, window, cx);
  304        assert_eq!(editor.text(cx), "12cde6");
  305    });
  306}
  307
  308#[gpui::test]
  309fn test_ime_composition(cx: &mut TestAppContext) {
  310    init_test(cx, |_| {});
  311
  312    let buffer = cx.new(|cx| {
  313        let mut buffer = language::Buffer::local("abcde", cx);
  314        // Ensure automatic grouping doesn't occur.
  315        buffer.set_group_interval(Duration::ZERO);
  316        buffer
  317    });
  318
  319    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  320    cx.add_window(|window, cx| {
  321        let mut editor = build_editor(buffer.clone(), window, cx);
  322
  323        // Start a new IME composition.
  324        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  325        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  326        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  327        assert_eq!(editor.text(cx), "äbcde");
  328        assert_eq!(
  329            editor.marked_text_ranges(cx),
  330            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  331        );
  332
  333        // Finalize IME composition.
  334        editor.replace_text_in_range(None, "ā", window, cx);
  335        assert_eq!(editor.text(cx), "ābcde");
  336        assert_eq!(editor.marked_text_ranges(cx), None);
  337
  338        // IME composition edits are grouped and are undone/redone at once.
  339        editor.undo(&Default::default(), window, cx);
  340        assert_eq!(editor.text(cx), "abcde");
  341        assert_eq!(editor.marked_text_ranges(cx), None);
  342        editor.redo(&Default::default(), window, cx);
  343        assert_eq!(editor.text(cx), "ābcde");
  344        assert_eq!(editor.marked_text_ranges(cx), None);
  345
  346        // Start a new IME composition.
  347        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  348        assert_eq!(
  349            editor.marked_text_ranges(cx),
  350            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  351        );
  352
  353        // Undoing during an IME composition cancels it.
  354        editor.undo(&Default::default(), window, cx);
  355        assert_eq!(editor.text(cx), "ābcde");
  356        assert_eq!(editor.marked_text_ranges(cx), None);
  357
  358        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  359        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  360        assert_eq!(editor.text(cx), "ābcdè");
  361        assert_eq!(
  362            editor.marked_text_ranges(cx),
  363            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  364        );
  365
  366        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  367        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  368        assert_eq!(editor.text(cx), "ābcdę");
  369        assert_eq!(editor.marked_text_ranges(cx), None);
  370
  371        // Start a new IME composition with multiple cursors.
  372        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  373            s.select_ranges([
  374                OffsetUtf16(1)..OffsetUtf16(1),
  375                OffsetUtf16(3)..OffsetUtf16(3),
  376                OffsetUtf16(5)..OffsetUtf16(5),
  377            ])
  378        });
  379        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  380        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  381        assert_eq!(
  382            editor.marked_text_ranges(cx),
  383            Some(vec![
  384                OffsetUtf16(0)..OffsetUtf16(3),
  385                OffsetUtf16(4)..OffsetUtf16(7),
  386                OffsetUtf16(8)..OffsetUtf16(11)
  387            ])
  388        );
  389
  390        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  391        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  392        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  393        assert_eq!(
  394            editor.marked_text_ranges(cx),
  395            Some(vec![
  396                OffsetUtf16(1)..OffsetUtf16(2),
  397                OffsetUtf16(5)..OffsetUtf16(6),
  398                OffsetUtf16(9)..OffsetUtf16(10)
  399            ])
  400        );
  401
  402        // Finalize IME composition with multiple cursors.
  403        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  404        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  405        assert_eq!(editor.marked_text_ranges(cx), None);
  406
  407        editor
  408    });
  409}
  410
  411#[gpui::test]
  412fn test_selection_with_mouse(cx: &mut TestAppContext) {
  413    init_test(cx, |_| {});
  414
  415    let editor = cx.add_window(|window, cx| {
  416        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  417        build_editor(buffer, window, cx)
  418    });
  419
  420    _ = editor.update(cx, |editor, window, cx| {
  421        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  422    });
  423    assert_eq!(
  424        editor
  425            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  426            .unwrap(),
  427        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  428    );
  429
  430    _ = editor.update(cx, |editor, window, cx| {
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.update_selection(
  449            DisplayPoint::new(DisplayRow(1), 1),
  450            0,
  451            gpui::Point::<f32>::default(),
  452            window,
  453            cx,
  454        );
  455    });
  456
  457    assert_eq!(
  458        editor
  459            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  460            .unwrap(),
  461        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  462    );
  463
  464    _ = editor.update(cx, |editor, window, cx| {
  465        editor.end_selection(window, cx);
  466        editor.update_selection(
  467            DisplayPoint::new(DisplayRow(3), 3),
  468            0,
  469            gpui::Point::<f32>::default(),
  470            window,
  471            cx,
  472        );
  473    });
  474
  475    assert_eq!(
  476        editor
  477            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  478            .unwrap(),
  479        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  480    );
  481
  482    _ = editor.update(cx, |editor, window, cx| {
  483        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  484        editor.update_selection(
  485            DisplayPoint::new(DisplayRow(0), 0),
  486            0,
  487            gpui::Point::<f32>::default(),
  488            window,
  489            cx,
  490        );
  491    });
  492
  493    assert_eq!(
  494        editor
  495            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  496            .unwrap(),
  497        [
  498            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  499            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  500        ]
  501    );
  502
  503    _ = editor.update(cx, |editor, window, cx| {
  504        editor.end_selection(window, cx);
  505    });
  506
  507    assert_eq!(
  508        editor
  509            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  510            .unwrap(),
  511        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  512    );
  513}
  514
  515#[gpui::test]
  516fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  517    init_test(cx, |_| {});
  518
  519    let editor = cx.add_window(|window, cx| {
  520        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  521        build_editor(buffer, window, cx)
  522    });
  523
  524    _ = editor.update(cx, |editor, window, cx| {
  525        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  526    });
  527
  528    _ = editor.update(cx, |editor, window, cx| {
  529        editor.end_selection(window, cx);
  530    });
  531
  532    _ = editor.update(cx, |editor, window, cx| {
  533        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  534    });
  535
  536    _ = editor.update(cx, |editor, window, cx| {
  537        editor.end_selection(window, cx);
  538    });
  539
  540    assert_eq!(
  541        editor
  542            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  543            .unwrap(),
  544        [
  545            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  546            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  547        ]
  548    );
  549
  550    _ = editor.update(cx, |editor, window, cx| {
  551        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  552    });
  553
  554    _ = editor.update(cx, |editor, window, cx| {
  555        editor.end_selection(window, cx);
  556    });
  557
  558    assert_eq!(
  559        editor
  560            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  561            .unwrap(),
  562        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  563    );
  564}
  565
  566#[gpui::test]
  567fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  568    init_test(cx, |_| {});
  569
  570    let editor = cx.add_window(|window, cx| {
  571        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  572        build_editor(buffer, window, cx)
  573    });
  574
  575    _ = editor.update(cx, |editor, window, cx| {
  576        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  577        assert_eq!(
  578            display_ranges(editor, cx),
  579            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  580        );
  581    });
  582
  583    _ = editor.update(cx, |editor, window, cx| {
  584        editor.update_selection(
  585            DisplayPoint::new(DisplayRow(3), 3),
  586            0,
  587            gpui::Point::<f32>::default(),
  588            window,
  589            cx,
  590        );
  591        assert_eq!(
  592            display_ranges(editor, cx),
  593            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  594        );
  595    });
  596
  597    _ = editor.update(cx, |editor, window, cx| {
  598        editor.cancel(&Cancel, window, cx);
  599        editor.update_selection(
  600            DisplayPoint::new(DisplayRow(1), 1),
  601            0,
  602            gpui::Point::<f32>::default(),
  603            window,
  604            cx,
  605        );
  606        assert_eq!(
  607            display_ranges(editor, cx),
  608            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  609        );
  610    });
  611}
  612
  613#[gpui::test]
  614fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  615    init_test(cx, |_| {});
  616
  617    let editor = cx.add_window(|window, cx| {
  618        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  619        build_editor(buffer, window, cx)
  620    });
  621
  622    _ = editor.update(cx, |editor, window, cx| {
  623        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  624        assert_eq!(
  625            display_ranges(editor, cx),
  626            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  627        );
  628
  629        editor.move_down(&Default::default(), window, cx);
  630        assert_eq!(
  631            display_ranges(editor, cx),
  632            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  633        );
  634
  635        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  636        assert_eq!(
  637            display_ranges(editor, cx),
  638            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  639        );
  640
  641        editor.move_up(&Default::default(), window, cx);
  642        assert_eq!(
  643            display_ranges(editor, cx),
  644            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  645        );
  646    });
  647}
  648
  649#[gpui::test]
  650fn test_extending_selection(cx: &mut TestAppContext) {
  651    init_test(cx, |_| {});
  652
  653    let editor = cx.add_window(|window, cx| {
  654        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  655        build_editor(buffer, window, cx)
  656    });
  657
  658    _ = editor.update(cx, |editor, window, cx| {
  659        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  660        editor.end_selection(window, cx);
  661        assert_eq!(
  662            display_ranges(editor, cx),
  663            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  664        );
  665
  666        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  667        editor.end_selection(window, cx);
  668        assert_eq!(
  669            display_ranges(editor, cx),
  670            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  671        );
  672
  673        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  674        editor.end_selection(window, cx);
  675        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  676        assert_eq!(
  677            display_ranges(editor, cx),
  678            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  679        );
  680
  681        editor.update_selection(
  682            DisplayPoint::new(DisplayRow(0), 1),
  683            0,
  684            gpui::Point::<f32>::default(),
  685            window,
  686            cx,
  687        );
  688        editor.end_selection(window, cx);
  689        assert_eq!(
  690            display_ranges(editor, cx),
  691            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  692        );
  693
  694        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  695        editor.end_selection(window, cx);
  696        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  697        editor.end_selection(window, cx);
  698        assert_eq!(
  699            display_ranges(editor, cx),
  700            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  701        );
  702
  703        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  704        assert_eq!(
  705            display_ranges(editor, cx),
  706            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  707        );
  708
  709        editor.update_selection(
  710            DisplayPoint::new(DisplayRow(0), 6),
  711            0,
  712            gpui::Point::<f32>::default(),
  713            window,
  714            cx,
  715        );
  716        assert_eq!(
  717            display_ranges(editor, cx),
  718            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  719        );
  720
  721        editor.update_selection(
  722            DisplayPoint::new(DisplayRow(0), 1),
  723            0,
  724            gpui::Point::<f32>::default(),
  725            window,
  726            cx,
  727        );
  728        editor.end_selection(window, cx);
  729        assert_eq!(
  730            display_ranges(editor, cx),
  731            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  732        );
  733    });
  734}
  735
  736#[gpui::test]
  737fn test_clone(cx: &mut TestAppContext) {
  738    init_test(cx, |_| {});
  739
  740    let (text, selection_ranges) = marked_text_ranges(
  741        indoc! {"
  742            one
  743            two
  744            threeˇ
  745            four
  746            fiveˇ
  747        "},
  748        true,
  749    );
  750
  751    let editor = cx.add_window(|window, cx| {
  752        let buffer = MultiBuffer::build_simple(&text, cx);
  753        build_editor(buffer, window, cx)
  754    });
  755
  756    _ = editor.update(cx, |editor, window, cx| {
  757        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  758            s.select_ranges(selection_ranges.clone())
  759        });
  760        editor.fold_creases(
  761            vec![
  762                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  763                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  764            ],
  765            true,
  766            window,
  767            cx,
  768        );
  769    });
  770
  771    let cloned_editor = editor
  772        .update(cx, |editor, _, cx| {
  773            cx.open_window(Default::default(), |window, cx| {
  774                cx.new(|cx| editor.clone(window, cx))
  775            })
  776        })
  777        .unwrap()
  778        .unwrap();
  779
  780    let snapshot = editor
  781        .update(cx, |e, window, cx| e.snapshot(window, cx))
  782        .unwrap();
  783    let cloned_snapshot = cloned_editor
  784        .update(cx, |e, window, cx| e.snapshot(window, cx))
  785        .unwrap();
  786
  787    assert_eq!(
  788        cloned_editor
  789            .update(cx, |e, _, cx| e.display_text(cx))
  790            .unwrap(),
  791        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  792    );
  793    assert_eq!(
  794        cloned_snapshot
  795            .folds_in_range(0..text.len())
  796            .collect::<Vec<_>>(),
  797        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  798    );
  799    assert_set_eq!(
  800        cloned_editor
  801            .update(cx, |editor, _, cx| editor
  802                .selections
  803                .ranges::<Point>(&editor.display_snapshot(cx)))
  804            .unwrap(),
  805        editor
  806            .update(cx, |editor, _, cx| editor
  807                .selections
  808                .ranges(&editor.display_snapshot(cx)))
  809            .unwrap()
  810    );
  811    assert_set_eq!(
  812        cloned_editor
  813            .update(cx, |e, _window, cx| e
  814                .selections
  815                .display_ranges(&e.display_snapshot(cx)))
  816            .unwrap(),
  817        editor
  818            .update(cx, |e, _, cx| e
  819                .selections
  820                .display_ranges(&e.display_snapshot(cx)))
  821            .unwrap()
  822    );
  823}
  824
  825#[gpui::test]
  826async fn test_navigation_history(cx: &mut TestAppContext) {
  827    init_test(cx, |_| {});
  828
  829    use workspace::item::Item;
  830
  831    let fs = FakeFs::new(cx.executor());
  832    let project = Project::test(fs, [], cx).await;
  833    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  834    let pane = workspace
  835        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  836        .unwrap();
  837
  838    _ = workspace.update(cx, |_v, window, cx| {
  839        cx.new(|cx| {
  840            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  841            let mut editor = build_editor(buffer, window, cx);
  842            let handle = cx.entity();
  843            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  844
  845            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  846                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  847            }
  848
  849            // Move the cursor a small distance.
  850            // Nothing is added to the navigation history.
  851            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  852                s.select_display_ranges([
  853                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  854                ])
  855            });
  856            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  857                s.select_display_ranges([
  858                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  859                ])
  860            });
  861            assert!(pop_history(&mut editor, cx).is_none());
  862
  863            // Move the cursor a large distance.
  864            // The history can jump back to the previous position.
  865            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  866                s.select_display_ranges([
  867                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  868                ])
  869            });
  870            let nav_entry = pop_history(&mut editor, cx).unwrap();
  871            editor.navigate(nav_entry.data.unwrap(), window, cx);
  872            assert_eq!(nav_entry.item.id(), cx.entity_id());
  873            assert_eq!(
  874                editor
  875                    .selections
  876                    .display_ranges(&editor.display_snapshot(cx)),
  877                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  878            );
  879            assert!(pop_history(&mut editor, cx).is_none());
  880
  881            // Move the cursor a small distance via the mouse.
  882            // Nothing is added to the navigation history.
  883            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  884            editor.end_selection(window, cx);
  885            assert_eq!(
  886                editor
  887                    .selections
  888                    .display_ranges(&editor.display_snapshot(cx)),
  889                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  890            );
  891            assert!(pop_history(&mut editor, cx).is_none());
  892
  893            // Move the cursor a large distance via the mouse.
  894            // The history can jump back to the previous position.
  895            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  896            editor.end_selection(window, cx);
  897            assert_eq!(
  898                editor
  899                    .selections
  900                    .display_ranges(&editor.display_snapshot(cx)),
  901                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  902            );
  903            let nav_entry = pop_history(&mut editor, cx).unwrap();
  904            editor.navigate(nav_entry.data.unwrap(), window, cx);
  905            assert_eq!(nav_entry.item.id(), cx.entity_id());
  906            assert_eq!(
  907                editor
  908                    .selections
  909                    .display_ranges(&editor.display_snapshot(cx)),
  910                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  911            );
  912            assert!(pop_history(&mut editor, cx).is_none());
  913
  914            // Set scroll position to check later
  915            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  916            let original_scroll_position = editor.scroll_manager.anchor();
  917
  918            // Jump to the end of the document and adjust scroll
  919            editor.move_to_end(&MoveToEnd, window, cx);
  920            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  921            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  922
  923            let nav_entry = pop_history(&mut editor, cx).unwrap();
  924            editor.navigate(nav_entry.data.unwrap(), window, cx);
  925            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  926
  927            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  928            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  929            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  930            let invalid_point = Point::new(9999, 0);
  931            editor.navigate(
  932                Box::new(NavigationData {
  933                    cursor_anchor: invalid_anchor,
  934                    cursor_position: invalid_point,
  935                    scroll_anchor: ScrollAnchor {
  936                        anchor: invalid_anchor,
  937                        offset: Default::default(),
  938                    },
  939                    scroll_top_row: invalid_point.row,
  940                }),
  941                window,
  942                cx,
  943            );
  944            assert_eq!(
  945                editor
  946                    .selections
  947                    .display_ranges(&editor.display_snapshot(cx)),
  948                &[editor.max_point(cx)..editor.max_point(cx)]
  949            );
  950            assert_eq!(
  951                editor.scroll_position(cx),
  952                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  953            );
  954
  955            editor
  956        })
  957    });
  958}
  959
  960#[gpui::test]
  961fn test_cancel(cx: &mut TestAppContext) {
  962    init_test(cx, |_| {});
  963
  964    let editor = cx.add_window(|window, cx| {
  965        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  966        build_editor(buffer, window, cx)
  967    });
  968
  969    _ = editor.update(cx, |editor, window, cx| {
  970        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  971        editor.update_selection(
  972            DisplayPoint::new(DisplayRow(1), 1),
  973            0,
  974            gpui::Point::<f32>::default(),
  975            window,
  976            cx,
  977        );
  978        editor.end_selection(window, cx);
  979
  980        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  981        editor.update_selection(
  982            DisplayPoint::new(DisplayRow(0), 3),
  983            0,
  984            gpui::Point::<f32>::default(),
  985            window,
  986            cx,
  987        );
  988        editor.end_selection(window, cx);
  989        assert_eq!(
  990            display_ranges(editor, cx),
  991            [
  992                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  993                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  994            ]
  995        );
  996    });
  997
  998    _ = editor.update(cx, |editor, window, cx| {
  999        editor.cancel(&Cancel, window, cx);
 1000        assert_eq!(
 1001            display_ranges(editor, cx),
 1002            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1003        );
 1004    });
 1005
 1006    _ = editor.update(cx, |editor, window, cx| {
 1007        editor.cancel(&Cancel, window, cx);
 1008        assert_eq!(
 1009            display_ranges(editor, cx),
 1010            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1011        );
 1012    });
 1013}
 1014
 1015#[gpui::test]
 1016fn test_fold_action(cx: &mut TestAppContext) {
 1017    init_test(cx, |_| {});
 1018
 1019    let editor = cx.add_window(|window, cx| {
 1020        let buffer = MultiBuffer::build_simple(
 1021            &"
 1022                impl Foo {
 1023                    // Hello!
 1024
 1025                    fn a() {
 1026                        1
 1027                    }
 1028
 1029                    fn b() {
 1030                        2
 1031                    }
 1032
 1033                    fn c() {
 1034                        3
 1035                    }
 1036                }
 1037            "
 1038            .unindent(),
 1039            cx,
 1040        );
 1041        build_editor(buffer, window, cx)
 1042    });
 1043
 1044    _ = editor.update(cx, |editor, window, cx| {
 1045        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1046            s.select_display_ranges([
 1047                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1048            ]);
 1049        });
 1050        editor.fold(&Fold, window, cx);
 1051        assert_eq!(
 1052            editor.display_text(cx),
 1053            "
 1054                impl Foo {
 1055                    // Hello!
 1056
 1057                    fn a() {
 1058                        1
 1059                    }
 1060
 1061                    fn b() {⋯
 1062                    }
 1063
 1064                    fn c() {⋯
 1065                    }
 1066                }
 1067            "
 1068            .unindent(),
 1069        );
 1070
 1071        editor.fold(&Fold, window, cx);
 1072        assert_eq!(
 1073            editor.display_text(cx),
 1074            "
 1075                impl Foo {⋯
 1076                }
 1077            "
 1078            .unindent(),
 1079        );
 1080
 1081        editor.unfold_lines(&UnfoldLines, window, cx);
 1082        assert_eq!(
 1083            editor.display_text(cx),
 1084            "
 1085                impl Foo {
 1086                    // Hello!
 1087
 1088                    fn a() {
 1089                        1
 1090                    }
 1091
 1092                    fn b() {⋯
 1093                    }
 1094
 1095                    fn c() {⋯
 1096                    }
 1097                }
 1098            "
 1099            .unindent(),
 1100        );
 1101
 1102        editor.unfold_lines(&UnfoldLines, window, cx);
 1103        assert_eq!(
 1104            editor.display_text(cx),
 1105            editor.buffer.read(cx).read(cx).text()
 1106        );
 1107    });
 1108}
 1109
 1110#[gpui::test]
 1111fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1112    init_test(cx, |_| {});
 1113
 1114    let editor = cx.add_window(|window, cx| {
 1115        let buffer = MultiBuffer::build_simple(
 1116            &"
 1117                class Foo:
 1118                    # Hello!
 1119
 1120                    def a():
 1121                        print(1)
 1122
 1123                    def b():
 1124                        print(2)
 1125
 1126                    def c():
 1127                        print(3)
 1128            "
 1129            .unindent(),
 1130            cx,
 1131        );
 1132        build_editor(buffer, window, cx)
 1133    });
 1134
 1135    _ = editor.update(cx, |editor, window, cx| {
 1136        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1137            s.select_display_ranges([
 1138                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1139            ]);
 1140        });
 1141        editor.fold(&Fold, window, cx);
 1142        assert_eq!(
 1143            editor.display_text(cx),
 1144            "
 1145                class Foo:
 1146                    # Hello!
 1147
 1148                    def a():
 1149                        print(1)
 1150
 1151                    def b():⋯
 1152
 1153                    def c():⋯
 1154            "
 1155            .unindent(),
 1156        );
 1157
 1158        editor.fold(&Fold, window, cx);
 1159        assert_eq!(
 1160            editor.display_text(cx),
 1161            "
 1162                class Foo:⋯
 1163            "
 1164            .unindent(),
 1165        );
 1166
 1167        editor.unfold_lines(&UnfoldLines, window, cx);
 1168        assert_eq!(
 1169            editor.display_text(cx),
 1170            "
 1171                class Foo:
 1172                    # Hello!
 1173
 1174                    def a():
 1175                        print(1)
 1176
 1177                    def b():⋯
 1178
 1179                    def c():⋯
 1180            "
 1181            .unindent(),
 1182        );
 1183
 1184        editor.unfold_lines(&UnfoldLines, window, cx);
 1185        assert_eq!(
 1186            editor.display_text(cx),
 1187            editor.buffer.read(cx).read(cx).text()
 1188        );
 1189    });
 1190}
 1191
 1192#[gpui::test]
 1193fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1194    init_test(cx, |_| {});
 1195
 1196    let editor = cx.add_window(|window, cx| {
 1197        let buffer = MultiBuffer::build_simple(
 1198            &"
 1199                class Foo:
 1200                    # Hello!
 1201
 1202                    def a():
 1203                        print(1)
 1204
 1205                    def b():
 1206                        print(2)
 1207
 1208
 1209                    def c():
 1210                        print(3)
 1211
 1212
 1213            "
 1214            .unindent(),
 1215            cx,
 1216        );
 1217        build_editor(buffer, window, cx)
 1218    });
 1219
 1220    _ = editor.update(cx, |editor, window, cx| {
 1221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1222            s.select_display_ranges([
 1223                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1224            ]);
 1225        });
 1226        editor.fold(&Fold, window, cx);
 1227        assert_eq!(
 1228            editor.display_text(cx),
 1229            "
 1230                class Foo:
 1231                    # Hello!
 1232
 1233                    def a():
 1234                        print(1)
 1235
 1236                    def b():⋯
 1237
 1238
 1239                    def c():⋯
 1240
 1241
 1242            "
 1243            .unindent(),
 1244        );
 1245
 1246        editor.fold(&Fold, window, cx);
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            "
 1250                class Foo:⋯
 1251
 1252
 1253            "
 1254            .unindent(),
 1255        );
 1256
 1257        editor.unfold_lines(&UnfoldLines, window, cx);
 1258        assert_eq!(
 1259            editor.display_text(cx),
 1260            "
 1261                class Foo:
 1262                    # Hello!
 1263
 1264                    def a():
 1265                        print(1)
 1266
 1267                    def b():⋯
 1268
 1269
 1270                    def c():⋯
 1271
 1272
 1273            "
 1274            .unindent(),
 1275        );
 1276
 1277        editor.unfold_lines(&UnfoldLines, window, cx);
 1278        assert_eq!(
 1279            editor.display_text(cx),
 1280            editor.buffer.read(cx).read(cx).text()
 1281        );
 1282    });
 1283}
 1284
 1285#[gpui::test]
 1286fn test_fold_at_level(cx: &mut TestAppContext) {
 1287    init_test(cx, |_| {});
 1288
 1289    let editor = cx.add_window(|window, cx| {
 1290        let buffer = MultiBuffer::build_simple(
 1291            &"
 1292                class Foo:
 1293                    # Hello!
 1294
 1295                    def a():
 1296                        print(1)
 1297
 1298                    def b():
 1299                        print(2)
 1300
 1301
 1302                class Bar:
 1303                    # World!
 1304
 1305                    def a():
 1306                        print(1)
 1307
 1308                    def b():
 1309                        print(2)
 1310
 1311
 1312            "
 1313            .unindent(),
 1314            cx,
 1315        );
 1316        build_editor(buffer, window, cx)
 1317    });
 1318
 1319    _ = editor.update(cx, |editor, window, cx| {
 1320        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1321        assert_eq!(
 1322            editor.display_text(cx),
 1323            "
 1324                class Foo:
 1325                    # Hello!
 1326
 1327                    def a():⋯
 1328
 1329                    def b():⋯
 1330
 1331
 1332                class Bar:
 1333                    # World!
 1334
 1335                    def a():⋯
 1336
 1337                    def b():⋯
 1338
 1339
 1340            "
 1341            .unindent(),
 1342        );
 1343
 1344        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1345        assert_eq!(
 1346            editor.display_text(cx),
 1347            "
 1348                class Foo:⋯
 1349
 1350
 1351                class Bar:⋯
 1352
 1353
 1354            "
 1355            .unindent(),
 1356        );
 1357
 1358        editor.unfold_all(&UnfoldAll, window, cx);
 1359        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1360        assert_eq!(
 1361            editor.display_text(cx),
 1362            "
 1363                class Foo:
 1364                    # Hello!
 1365
 1366                    def a():
 1367                        print(1)
 1368
 1369                    def b():
 1370                        print(2)
 1371
 1372
 1373                class Bar:
 1374                    # World!
 1375
 1376                    def a():
 1377                        print(1)
 1378
 1379                    def b():
 1380                        print(2)
 1381
 1382
 1383            "
 1384            .unindent(),
 1385        );
 1386
 1387        assert_eq!(
 1388            editor.display_text(cx),
 1389            editor.buffer.read(cx).read(cx).text()
 1390        );
 1391        let (_, positions) = marked_text_ranges(
 1392            &"
 1393                       class Foo:
 1394                           # Hello!
 1395
 1396                           def a():
 1397                              print(1)
 1398
 1399                           def b():
 1400                               p«riˇ»nt(2)
 1401
 1402
 1403                       class Bar:
 1404                           # World!
 1405
 1406                           def a():
 1407                               «ˇprint(1)
 1408
 1409                           def b():
 1410                               print(2)»
 1411
 1412
 1413                   "
 1414            .unindent(),
 1415            true,
 1416        );
 1417
 1418        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1419            s.select_ranges(positions)
 1420        });
 1421
 1422        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1423        assert_eq!(
 1424            editor.display_text(cx),
 1425            "
 1426                class Foo:
 1427                    # Hello!
 1428
 1429                    def a():⋯
 1430
 1431                    def b():
 1432                        print(2)
 1433
 1434
 1435                class Bar:
 1436                    # World!
 1437
 1438                    def a():
 1439                        print(1)
 1440
 1441                    def b():
 1442                        print(2)
 1443
 1444
 1445            "
 1446            .unindent(),
 1447        );
 1448    });
 1449}
 1450
 1451#[gpui::test]
 1452fn test_move_cursor(cx: &mut TestAppContext) {
 1453    init_test(cx, |_| {});
 1454
 1455    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1456    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1457
 1458    buffer.update(cx, |buffer, cx| {
 1459        buffer.edit(
 1460            vec![
 1461                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1462                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1463            ],
 1464            None,
 1465            cx,
 1466        );
 1467    });
 1468    _ = editor.update(cx, |editor, window, cx| {
 1469        assert_eq!(
 1470            display_ranges(editor, cx),
 1471            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1472        );
 1473
 1474        editor.move_down(&MoveDown, window, cx);
 1475        assert_eq!(
 1476            display_ranges(editor, cx),
 1477            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1478        );
 1479
 1480        editor.move_right(&MoveRight, window, cx);
 1481        assert_eq!(
 1482            display_ranges(editor, cx),
 1483            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1484        );
 1485
 1486        editor.move_left(&MoveLeft, window, cx);
 1487        assert_eq!(
 1488            display_ranges(editor, cx),
 1489            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1490        );
 1491
 1492        editor.move_up(&MoveUp, window, cx);
 1493        assert_eq!(
 1494            display_ranges(editor, cx),
 1495            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1496        );
 1497
 1498        editor.move_to_end(&MoveToEnd, window, cx);
 1499        assert_eq!(
 1500            display_ranges(editor, cx),
 1501            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1502        );
 1503
 1504        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1505        assert_eq!(
 1506            display_ranges(editor, cx),
 1507            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1508        );
 1509
 1510        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1511            s.select_display_ranges([
 1512                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1513            ]);
 1514        });
 1515        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1516        assert_eq!(
 1517            display_ranges(editor, cx),
 1518            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1519        );
 1520
 1521        editor.select_to_end(&SelectToEnd, window, cx);
 1522        assert_eq!(
 1523            display_ranges(editor, cx),
 1524            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1525        );
 1526    });
 1527}
 1528
 1529#[gpui::test]
 1530fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1531    init_test(cx, |_| {});
 1532
 1533    let editor = cx.add_window(|window, cx| {
 1534        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1535        build_editor(buffer, window, cx)
 1536    });
 1537
 1538    assert_eq!('🟥'.len_utf8(), 4);
 1539    assert_eq!('α'.len_utf8(), 2);
 1540
 1541    _ = editor.update(cx, |editor, window, cx| {
 1542        editor.fold_creases(
 1543            vec![
 1544                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1545                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1546                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1547            ],
 1548            true,
 1549            window,
 1550            cx,
 1551        );
 1552        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1553
 1554        editor.move_right(&MoveRight, window, cx);
 1555        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1556        editor.move_right(&MoveRight, window, cx);
 1557        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1558        editor.move_right(&MoveRight, window, cx);
 1559        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1560
 1561        editor.move_down(&MoveDown, window, cx);
 1562        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1563        editor.move_left(&MoveLeft, window, cx);
 1564        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1565        editor.move_left(&MoveLeft, window, cx);
 1566        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1567        editor.move_left(&MoveLeft, window, cx);
 1568        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1569
 1570        editor.move_down(&MoveDown, window, cx);
 1571        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1572        editor.move_right(&MoveRight, window, cx);
 1573        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1574        editor.move_right(&MoveRight, window, cx);
 1575        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1576        editor.move_right(&MoveRight, window, cx);
 1577        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1578
 1579        editor.move_up(&MoveUp, window, cx);
 1580        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1581        editor.move_down(&MoveDown, window, cx);
 1582        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1583        editor.move_up(&MoveUp, window, cx);
 1584        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1585
 1586        editor.move_up(&MoveUp, window, cx);
 1587        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1588        editor.move_left(&MoveLeft, window, cx);
 1589        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1590        editor.move_left(&MoveLeft, window, cx);
 1591        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1592    });
 1593}
 1594
 1595#[gpui::test]
 1596fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1597    init_test(cx, |_| {});
 1598
 1599    let editor = cx.add_window(|window, cx| {
 1600        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1601        build_editor(buffer, window, cx)
 1602    });
 1603    _ = editor.update(cx, |editor, window, cx| {
 1604        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1605            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1606        });
 1607
 1608        // moving above start of document should move selection to start of document,
 1609        // but the next move down should still be at the original goal_x
 1610        editor.move_up(&MoveUp, window, cx);
 1611        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1612
 1613        editor.move_down(&MoveDown, window, cx);
 1614        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1615
 1616        editor.move_down(&MoveDown, window, cx);
 1617        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1618
 1619        editor.move_down(&MoveDown, window, cx);
 1620        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1621
 1622        editor.move_down(&MoveDown, window, cx);
 1623        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1624
 1625        // moving past end of document should not change goal_x
 1626        editor.move_down(&MoveDown, window, cx);
 1627        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1628
 1629        editor.move_down(&MoveDown, window, cx);
 1630        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1631
 1632        editor.move_up(&MoveUp, window, cx);
 1633        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1634
 1635        editor.move_up(&MoveUp, window, cx);
 1636        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1637
 1638        editor.move_up(&MoveUp, window, cx);
 1639        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1640    });
 1641}
 1642
 1643#[gpui::test]
 1644fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1645    init_test(cx, |_| {});
 1646    let move_to_beg = MoveToBeginningOfLine {
 1647        stop_at_soft_wraps: true,
 1648        stop_at_indent: true,
 1649    };
 1650
 1651    let delete_to_beg = DeleteToBeginningOfLine {
 1652        stop_at_indent: false,
 1653    };
 1654
 1655    let move_to_end = MoveToEndOfLine {
 1656        stop_at_soft_wraps: true,
 1657    };
 1658
 1659    let editor = cx.add_window(|window, cx| {
 1660        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1661        build_editor(buffer, window, cx)
 1662    });
 1663    _ = editor.update(cx, |editor, window, cx| {
 1664        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1665            s.select_display_ranges([
 1666                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1667                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1668            ]);
 1669        });
 1670    });
 1671
 1672    _ = editor.update(cx, |editor, window, cx| {
 1673        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1674        assert_eq!(
 1675            display_ranges(editor, cx),
 1676            &[
 1677                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1678                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1679            ]
 1680        );
 1681    });
 1682
 1683    _ = editor.update(cx, |editor, window, cx| {
 1684        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1685        assert_eq!(
 1686            display_ranges(editor, cx),
 1687            &[
 1688                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1689                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1690            ]
 1691        );
 1692    });
 1693
 1694    _ = editor.update(cx, |editor, window, cx| {
 1695        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1696        assert_eq!(
 1697            display_ranges(editor, cx),
 1698            &[
 1699                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1700                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1701            ]
 1702        );
 1703    });
 1704
 1705    _ = editor.update(cx, |editor, window, cx| {
 1706        editor.move_to_end_of_line(&move_to_end, window, cx);
 1707        assert_eq!(
 1708            display_ranges(editor, cx),
 1709            &[
 1710                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1711                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1712            ]
 1713        );
 1714    });
 1715
 1716    // Moving to the end of line again is a no-op.
 1717    _ = editor.update(cx, |editor, window, cx| {
 1718        editor.move_to_end_of_line(&move_to_end, window, cx);
 1719        assert_eq!(
 1720            display_ranges(editor, cx),
 1721            &[
 1722                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1723                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1724            ]
 1725        );
 1726    });
 1727
 1728    _ = editor.update(cx, |editor, window, cx| {
 1729        editor.move_left(&MoveLeft, window, cx);
 1730        editor.select_to_beginning_of_line(
 1731            &SelectToBeginningOfLine {
 1732                stop_at_soft_wraps: true,
 1733                stop_at_indent: true,
 1734            },
 1735            window,
 1736            cx,
 1737        );
 1738        assert_eq!(
 1739            display_ranges(editor, cx),
 1740            &[
 1741                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1742                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1743            ]
 1744        );
 1745    });
 1746
 1747    _ = editor.update(cx, |editor, window, cx| {
 1748        editor.select_to_beginning_of_line(
 1749            &SelectToBeginningOfLine {
 1750                stop_at_soft_wraps: true,
 1751                stop_at_indent: true,
 1752            },
 1753            window,
 1754            cx,
 1755        );
 1756        assert_eq!(
 1757            display_ranges(editor, cx),
 1758            &[
 1759                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1760                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1761            ]
 1762        );
 1763    });
 1764
 1765    _ = editor.update(cx, |editor, window, cx| {
 1766        editor.select_to_beginning_of_line(
 1767            &SelectToBeginningOfLine {
 1768                stop_at_soft_wraps: true,
 1769                stop_at_indent: true,
 1770            },
 1771            window,
 1772            cx,
 1773        );
 1774        assert_eq!(
 1775            display_ranges(editor, cx),
 1776            &[
 1777                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1778                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1779            ]
 1780        );
 1781    });
 1782
 1783    _ = editor.update(cx, |editor, window, cx| {
 1784        editor.select_to_end_of_line(
 1785            &SelectToEndOfLine {
 1786                stop_at_soft_wraps: true,
 1787            },
 1788            window,
 1789            cx,
 1790        );
 1791        assert_eq!(
 1792            display_ranges(editor, cx),
 1793            &[
 1794                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1795                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1796            ]
 1797        );
 1798    });
 1799
 1800    _ = editor.update(cx, |editor, window, cx| {
 1801        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1802        assert_eq!(editor.display_text(cx), "ab\n  de");
 1803        assert_eq!(
 1804            display_ranges(editor, cx),
 1805            &[
 1806                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1807                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1808            ]
 1809        );
 1810    });
 1811
 1812    _ = editor.update(cx, |editor, window, cx| {
 1813        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1814        assert_eq!(editor.display_text(cx), "\n");
 1815        assert_eq!(
 1816            display_ranges(editor, cx),
 1817            &[
 1818                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1819                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1820            ]
 1821        );
 1822    });
 1823}
 1824
 1825#[gpui::test]
 1826fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1827    init_test(cx, |_| {});
 1828    let move_to_beg = MoveToBeginningOfLine {
 1829        stop_at_soft_wraps: false,
 1830        stop_at_indent: false,
 1831    };
 1832
 1833    let move_to_end = MoveToEndOfLine {
 1834        stop_at_soft_wraps: false,
 1835    };
 1836
 1837    let editor = cx.add_window(|window, cx| {
 1838        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1839        build_editor(buffer, window, cx)
 1840    });
 1841
 1842    _ = editor.update(cx, |editor, window, cx| {
 1843        editor.set_wrap_width(Some(140.0.into()), cx);
 1844
 1845        // We expect the following lines after wrapping
 1846        // ```
 1847        // thequickbrownfox
 1848        // jumpedoverthelazydo
 1849        // gs
 1850        // ```
 1851        // The final `gs` was soft-wrapped onto a new line.
 1852        assert_eq!(
 1853            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1854            editor.display_text(cx),
 1855        );
 1856
 1857        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1858        // Start the cursor at the `k` on the first line
 1859        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1860            s.select_display_ranges([
 1861                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1862            ]);
 1863        });
 1864
 1865        // Moving to the beginning of the line should put us at the beginning of the line.
 1866        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1867        assert_eq!(
 1868            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1869            display_ranges(editor, cx)
 1870        );
 1871
 1872        // Moving to the end of the line should put us at the end of the line.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        assert_eq!(
 1875            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1876            display_ranges(editor, cx)
 1877        );
 1878
 1879        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1880        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1881        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1882            s.select_display_ranges([
 1883                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1884            ]);
 1885        });
 1886
 1887        // Moving to the beginning of the line should put us at the start of the second line of
 1888        // display text, i.e., the `j`.
 1889        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1890        assert_eq!(
 1891            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1892            display_ranges(editor, cx)
 1893        );
 1894
 1895        // Moving to the beginning of the line again should be a no-op.
 1896        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1897        assert_eq!(
 1898            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1899            display_ranges(editor, cx)
 1900        );
 1901
 1902        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1903        // next display line.
 1904        editor.move_to_end_of_line(&move_to_end, window, cx);
 1905        assert_eq!(
 1906            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1907            display_ranges(editor, cx)
 1908        );
 1909
 1910        // Moving to the end of the line again should be a no-op.
 1911        editor.move_to_end_of_line(&move_to_end, window, cx);
 1912        assert_eq!(
 1913            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1914            display_ranges(editor, cx)
 1915        );
 1916    });
 1917}
 1918
 1919#[gpui::test]
 1920fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1921    init_test(cx, |_| {});
 1922
 1923    let move_to_beg = MoveToBeginningOfLine {
 1924        stop_at_soft_wraps: true,
 1925        stop_at_indent: true,
 1926    };
 1927
 1928    let select_to_beg = SelectToBeginningOfLine {
 1929        stop_at_soft_wraps: true,
 1930        stop_at_indent: true,
 1931    };
 1932
 1933    let delete_to_beg = DeleteToBeginningOfLine {
 1934        stop_at_indent: true,
 1935    };
 1936
 1937    let move_to_end = MoveToEndOfLine {
 1938        stop_at_soft_wraps: false,
 1939    };
 1940
 1941    let editor = cx.add_window(|window, cx| {
 1942        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1943        build_editor(buffer, window, cx)
 1944    });
 1945
 1946    _ = editor.update(cx, |editor, window, cx| {
 1947        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1948            s.select_display_ranges([
 1949                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1950                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1951            ]);
 1952        });
 1953
 1954        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1955        // and the second cursor at the first non-whitespace character in the line.
 1956        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1957        assert_eq!(
 1958            display_ranges(editor, cx),
 1959            &[
 1960                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1961                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1962            ]
 1963        );
 1964
 1965        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1966        // and should move the second cursor to the beginning of the line.
 1967        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1968        assert_eq!(
 1969            display_ranges(editor, cx),
 1970            &[
 1971                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1972                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1973            ]
 1974        );
 1975
 1976        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1977        // and should move the second cursor back to the first non-whitespace character in the line.
 1978        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1979        assert_eq!(
 1980            display_ranges(editor, cx),
 1981            &[
 1982                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1983                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1984            ]
 1985        );
 1986
 1987        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1988        // and to the first non-whitespace character in the line for the second cursor.
 1989        editor.move_to_end_of_line(&move_to_end, window, cx);
 1990        editor.move_left(&MoveLeft, window, cx);
 1991        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1992        assert_eq!(
 1993            display_ranges(editor, cx),
 1994            &[
 1995                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1996                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1997            ]
 1998        );
 1999
 2000        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2001        // and should select to the beginning of the line for the second cursor.
 2002        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2003        assert_eq!(
 2004            display_ranges(editor, cx),
 2005            &[
 2006                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2007                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2008            ]
 2009        );
 2010
 2011        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2012        // and should delete to the first non-whitespace character in the line for the second cursor.
 2013        editor.move_to_end_of_line(&move_to_end, window, cx);
 2014        editor.move_left(&MoveLeft, window, cx);
 2015        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2016        assert_eq!(editor.text(cx), "c\n  f");
 2017    });
 2018}
 2019
 2020#[gpui::test]
 2021fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2022    init_test(cx, |_| {});
 2023
 2024    let move_to_beg = MoveToBeginningOfLine {
 2025        stop_at_soft_wraps: true,
 2026        stop_at_indent: true,
 2027    };
 2028
 2029    let editor = cx.add_window(|window, cx| {
 2030        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2031        build_editor(buffer, window, cx)
 2032    });
 2033
 2034    _ = editor.update(cx, |editor, window, cx| {
 2035        // test cursor between line_start and indent_start
 2036        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2037            s.select_display_ranges([
 2038                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2039            ]);
 2040        });
 2041
 2042        // cursor should move to line_start
 2043        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2044        assert_eq!(
 2045            display_ranges(editor, cx),
 2046            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2047        );
 2048
 2049        // cursor should move to indent_start
 2050        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2051        assert_eq!(
 2052            display_ranges(editor, cx),
 2053            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2054        );
 2055
 2056        // cursor should move to back to line_start
 2057        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2058        assert_eq!(
 2059            display_ranges(editor, cx),
 2060            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2061        );
 2062    });
 2063}
 2064
 2065#[gpui::test]
 2066fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2067    init_test(cx, |_| {});
 2068
 2069    let editor = cx.add_window(|window, cx| {
 2070        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2071        build_editor(buffer, window, cx)
 2072    });
 2073    _ = editor.update(cx, |editor, window, cx| {
 2074        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2075            s.select_display_ranges([
 2076                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2077                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2078            ])
 2079        });
 2080        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2081        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2082
 2083        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2084        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2085
 2086        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2087        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2088
 2089        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2090        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2091
 2092        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2093        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2094
 2095        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2096        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2097
 2098        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2099        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2100
 2101        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2102        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2103
 2104        editor.move_right(&MoveRight, window, cx);
 2105        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2106        assert_selection_ranges(
 2107            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2108            editor,
 2109            cx,
 2110        );
 2111
 2112        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2113        assert_selection_ranges(
 2114            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2115            editor,
 2116            cx,
 2117        );
 2118
 2119        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2120        assert_selection_ranges(
 2121            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2122            editor,
 2123            cx,
 2124        );
 2125    });
 2126}
 2127
 2128#[gpui::test]
 2129fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2130    init_test(cx, |_| {});
 2131
 2132    let editor = cx.add_window(|window, cx| {
 2133        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2134        build_editor(buffer, window, cx)
 2135    });
 2136
 2137    _ = editor.update(cx, |editor, window, cx| {
 2138        editor.set_wrap_width(Some(140.0.into()), cx);
 2139        assert_eq!(
 2140            editor.display_text(cx),
 2141            "use one::{\n    two::three::\n    four::five\n};"
 2142        );
 2143
 2144        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2145            s.select_display_ranges([
 2146                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2147            ]);
 2148        });
 2149
 2150        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2151        assert_eq!(
 2152            display_ranges(editor, cx),
 2153            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2154        );
 2155
 2156        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2157        assert_eq!(
 2158            display_ranges(editor, cx),
 2159            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2160        );
 2161
 2162        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2163        assert_eq!(
 2164            display_ranges(editor, cx),
 2165            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2166        );
 2167
 2168        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2169        assert_eq!(
 2170            display_ranges(editor, cx),
 2171            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2172        );
 2173
 2174        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2175        assert_eq!(
 2176            display_ranges(editor, cx),
 2177            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2178        );
 2179
 2180        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2181        assert_eq!(
 2182            display_ranges(editor, cx),
 2183            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2184        );
 2185    });
 2186}
 2187
 2188#[gpui::test]
 2189async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2190    init_test(cx, |_| {});
 2191    let mut cx = EditorTestContext::new(cx).await;
 2192
 2193    let line_height = cx.editor(|editor, window, _| {
 2194        editor
 2195            .style()
 2196            .unwrap()
 2197            .text
 2198            .line_height_in_pixels(window.rem_size())
 2199    });
 2200    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2201
 2202    cx.set_state(
 2203        &r#"ˇone
 2204        two
 2205
 2206        three
 2207        fourˇ
 2208        five
 2209
 2210        six"#
 2211            .unindent(),
 2212    );
 2213
 2214    cx.update_editor(|editor, window, cx| {
 2215        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2216    });
 2217    cx.assert_editor_state(
 2218        &r#"one
 2219        two
 2220        ˇ
 2221        three
 2222        four
 2223        five
 2224        ˇ
 2225        six"#
 2226            .unindent(),
 2227    );
 2228
 2229    cx.update_editor(|editor, window, cx| {
 2230        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2231    });
 2232    cx.assert_editor_state(
 2233        &r#"one
 2234        two
 2235
 2236        three
 2237        four
 2238        five
 2239        ˇ
 2240        sixˇ"#
 2241            .unindent(),
 2242    );
 2243
 2244    cx.update_editor(|editor, window, cx| {
 2245        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2246    });
 2247    cx.assert_editor_state(
 2248        &r#"one
 2249        two
 2250
 2251        three
 2252        four
 2253        five
 2254
 2255        sixˇ"#
 2256            .unindent(),
 2257    );
 2258
 2259    cx.update_editor(|editor, window, cx| {
 2260        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2261    });
 2262    cx.assert_editor_state(
 2263        &r#"one
 2264        two
 2265
 2266        three
 2267        four
 2268        five
 2269        ˇ
 2270        six"#
 2271            .unindent(),
 2272    );
 2273
 2274    cx.update_editor(|editor, window, cx| {
 2275        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2276    });
 2277    cx.assert_editor_state(
 2278        &r#"one
 2279        two
 2280        ˇ
 2281        three
 2282        four
 2283        five
 2284
 2285        six"#
 2286            .unindent(),
 2287    );
 2288
 2289    cx.update_editor(|editor, window, cx| {
 2290        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2291    });
 2292    cx.assert_editor_state(
 2293        &r#"ˇone
 2294        two
 2295
 2296        three
 2297        four
 2298        five
 2299
 2300        six"#
 2301            .unindent(),
 2302    );
 2303}
 2304
 2305#[gpui::test]
 2306async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2307    init_test(cx, |_| {});
 2308    let mut cx = EditorTestContext::new(cx).await;
 2309    let line_height = cx.editor(|editor, window, _| {
 2310        editor
 2311            .style()
 2312            .unwrap()
 2313            .text
 2314            .line_height_in_pixels(window.rem_size())
 2315    });
 2316    let window = cx.window;
 2317    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2318
 2319    cx.set_state(
 2320        r#"ˇone
 2321        two
 2322        three
 2323        four
 2324        five
 2325        six
 2326        seven
 2327        eight
 2328        nine
 2329        ten
 2330        "#,
 2331    );
 2332
 2333    cx.update_editor(|editor, window, cx| {
 2334        assert_eq!(
 2335            editor.snapshot(window, cx).scroll_position(),
 2336            gpui::Point::new(0., 0.)
 2337        );
 2338        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2339        assert_eq!(
 2340            editor.snapshot(window, cx).scroll_position(),
 2341            gpui::Point::new(0., 3.)
 2342        );
 2343        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2344        assert_eq!(
 2345            editor.snapshot(window, cx).scroll_position(),
 2346            gpui::Point::new(0., 6.)
 2347        );
 2348        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2349        assert_eq!(
 2350            editor.snapshot(window, cx).scroll_position(),
 2351            gpui::Point::new(0., 3.)
 2352        );
 2353
 2354        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2355        assert_eq!(
 2356            editor.snapshot(window, cx).scroll_position(),
 2357            gpui::Point::new(0., 1.)
 2358        );
 2359        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2360        assert_eq!(
 2361            editor.snapshot(window, cx).scroll_position(),
 2362            gpui::Point::new(0., 3.)
 2363        );
 2364    });
 2365}
 2366
 2367#[gpui::test]
 2368async fn test_autoscroll(cx: &mut TestAppContext) {
 2369    init_test(cx, |_| {});
 2370    let mut cx = EditorTestContext::new(cx).await;
 2371
 2372    let line_height = cx.update_editor(|editor, window, cx| {
 2373        editor.set_vertical_scroll_margin(2, cx);
 2374        editor
 2375            .style()
 2376            .unwrap()
 2377            .text
 2378            .line_height_in_pixels(window.rem_size())
 2379    });
 2380    let window = cx.window;
 2381    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2382
 2383    cx.set_state(
 2384        r#"ˇone
 2385            two
 2386            three
 2387            four
 2388            five
 2389            six
 2390            seven
 2391            eight
 2392            nine
 2393            ten
 2394        "#,
 2395    );
 2396    cx.update_editor(|editor, window, cx| {
 2397        assert_eq!(
 2398            editor.snapshot(window, cx).scroll_position(),
 2399            gpui::Point::new(0., 0.0)
 2400        );
 2401    });
 2402
 2403    // Add a cursor below the visible area. Since both cursors cannot fit
 2404    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2405    // allows the vertical scroll margin below that cursor.
 2406    cx.update_editor(|editor, window, cx| {
 2407        editor.change_selections(Default::default(), window, cx, |selections| {
 2408            selections.select_ranges([
 2409                Point::new(0, 0)..Point::new(0, 0),
 2410                Point::new(6, 0)..Point::new(6, 0),
 2411            ]);
 2412        })
 2413    });
 2414    cx.update_editor(|editor, window, cx| {
 2415        assert_eq!(
 2416            editor.snapshot(window, cx).scroll_position(),
 2417            gpui::Point::new(0., 3.0)
 2418        );
 2419    });
 2420
 2421    // Move down. The editor cursor scrolls down to track the newest cursor.
 2422    cx.update_editor(|editor, window, cx| {
 2423        editor.move_down(&Default::default(), window, cx);
 2424    });
 2425    cx.update_editor(|editor, window, cx| {
 2426        assert_eq!(
 2427            editor.snapshot(window, cx).scroll_position(),
 2428            gpui::Point::new(0., 4.0)
 2429        );
 2430    });
 2431
 2432    // Add a cursor above the visible area. Since both cursors fit on screen,
 2433    // the editor scrolls to show both.
 2434    cx.update_editor(|editor, window, cx| {
 2435        editor.change_selections(Default::default(), window, cx, |selections| {
 2436            selections.select_ranges([
 2437                Point::new(1, 0)..Point::new(1, 0),
 2438                Point::new(6, 0)..Point::new(6, 0),
 2439            ]);
 2440        })
 2441    });
 2442    cx.update_editor(|editor, window, cx| {
 2443        assert_eq!(
 2444            editor.snapshot(window, cx).scroll_position(),
 2445            gpui::Point::new(0., 1.0)
 2446        );
 2447    });
 2448}
 2449
 2450#[gpui::test]
 2451async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2452    init_test(cx, |_| {});
 2453    let mut cx = EditorTestContext::new(cx).await;
 2454
 2455    let line_height = cx.editor(|editor, window, _cx| {
 2456        editor
 2457            .style()
 2458            .unwrap()
 2459            .text
 2460            .line_height_in_pixels(window.rem_size())
 2461    });
 2462    let window = cx.window;
 2463    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2464    cx.set_state(
 2465        &r#"
 2466        ˇone
 2467        two
 2468        threeˇ
 2469        four
 2470        five
 2471        six
 2472        seven
 2473        eight
 2474        nine
 2475        ten
 2476        "#
 2477        .unindent(),
 2478    );
 2479
 2480    cx.update_editor(|editor, window, cx| {
 2481        editor.move_page_down(&MovePageDown::default(), window, cx)
 2482    });
 2483    cx.assert_editor_state(
 2484        &r#"
 2485        one
 2486        two
 2487        three
 2488        ˇfour
 2489        five
 2490        sixˇ
 2491        seven
 2492        eight
 2493        nine
 2494        ten
 2495        "#
 2496        .unindent(),
 2497    );
 2498
 2499    cx.update_editor(|editor, window, cx| {
 2500        editor.move_page_down(&MovePageDown::default(), window, cx)
 2501    });
 2502    cx.assert_editor_state(
 2503        &r#"
 2504        one
 2505        two
 2506        three
 2507        four
 2508        five
 2509        six
 2510        ˇseven
 2511        eight
 2512        nineˇ
 2513        ten
 2514        "#
 2515        .unindent(),
 2516    );
 2517
 2518    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2519    cx.assert_editor_state(
 2520        &r#"
 2521        one
 2522        two
 2523        three
 2524        ˇfour
 2525        five
 2526        sixˇ
 2527        seven
 2528        eight
 2529        nine
 2530        ten
 2531        "#
 2532        .unindent(),
 2533    );
 2534
 2535    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2536    cx.assert_editor_state(
 2537        &r#"
 2538        ˇone
 2539        two
 2540        threeˇ
 2541        four
 2542        five
 2543        six
 2544        seven
 2545        eight
 2546        nine
 2547        ten
 2548        "#
 2549        .unindent(),
 2550    );
 2551
 2552    // Test select collapsing
 2553    cx.update_editor(|editor, window, cx| {
 2554        editor.move_page_down(&MovePageDown::default(), window, cx);
 2555        editor.move_page_down(&MovePageDown::default(), window, cx);
 2556        editor.move_page_down(&MovePageDown::default(), window, cx);
 2557    });
 2558    cx.assert_editor_state(
 2559        &r#"
 2560        one
 2561        two
 2562        three
 2563        four
 2564        five
 2565        six
 2566        seven
 2567        eight
 2568        nine
 2569        ˇten
 2570        ˇ"#
 2571        .unindent(),
 2572    );
 2573}
 2574
 2575#[gpui::test]
 2576async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2577    init_test(cx, |_| {});
 2578    let mut cx = EditorTestContext::new(cx).await;
 2579    cx.set_state("one «two threeˇ» four");
 2580    cx.update_editor(|editor, window, cx| {
 2581        editor.delete_to_beginning_of_line(
 2582            &DeleteToBeginningOfLine {
 2583                stop_at_indent: false,
 2584            },
 2585            window,
 2586            cx,
 2587        );
 2588        assert_eq!(editor.text(cx), " four");
 2589    });
 2590}
 2591
 2592#[gpui::test]
 2593async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2594    init_test(cx, |_| {});
 2595
 2596    let mut cx = EditorTestContext::new(cx).await;
 2597
 2598    // For an empty selection, the preceding word fragment is deleted.
 2599    // For non-empty selections, only selected characters are deleted.
 2600    cx.set_state("onˇe two t«hreˇ»e four");
 2601    cx.update_editor(|editor, window, cx| {
 2602        editor.delete_to_previous_word_start(
 2603            &DeleteToPreviousWordStart {
 2604                ignore_newlines: false,
 2605                ignore_brackets: false,
 2606            },
 2607            window,
 2608            cx,
 2609        );
 2610    });
 2611    cx.assert_editor_state("ˇe two tˇe four");
 2612
 2613    cx.set_state("e tˇwo te «fˇ»our");
 2614    cx.update_editor(|editor, window, cx| {
 2615        editor.delete_to_next_word_end(
 2616            &DeleteToNextWordEnd {
 2617                ignore_newlines: false,
 2618                ignore_brackets: false,
 2619            },
 2620            window,
 2621            cx,
 2622        );
 2623    });
 2624    cx.assert_editor_state("e tˇ te ˇour");
 2625}
 2626
 2627#[gpui::test]
 2628async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2629    init_test(cx, |_| {});
 2630
 2631    let mut cx = EditorTestContext::new(cx).await;
 2632
 2633    cx.set_state("here is some text    ˇwith a space");
 2634    cx.update_editor(|editor, window, cx| {
 2635        editor.delete_to_previous_word_start(
 2636            &DeleteToPreviousWordStart {
 2637                ignore_newlines: false,
 2638                ignore_brackets: true,
 2639            },
 2640            window,
 2641            cx,
 2642        );
 2643    });
 2644    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2645    cx.assert_editor_state("here is some textˇwith a space");
 2646
 2647    cx.set_state("here is some text    ˇwith a space");
 2648    cx.update_editor(|editor, window, cx| {
 2649        editor.delete_to_previous_word_start(
 2650            &DeleteToPreviousWordStart {
 2651                ignore_newlines: false,
 2652                ignore_brackets: false,
 2653            },
 2654            window,
 2655            cx,
 2656        );
 2657    });
 2658    cx.assert_editor_state("here is some textˇwith a space");
 2659
 2660    cx.set_state("here is some textˇ    with a space");
 2661    cx.update_editor(|editor, window, cx| {
 2662        editor.delete_to_next_word_end(
 2663            &DeleteToNextWordEnd {
 2664                ignore_newlines: false,
 2665                ignore_brackets: true,
 2666            },
 2667            window,
 2668            cx,
 2669        );
 2670    });
 2671    // Same happens in the other direction.
 2672    cx.assert_editor_state("here is some textˇwith a space");
 2673
 2674    cx.set_state("here is some textˇ    with a space");
 2675    cx.update_editor(|editor, window, cx| {
 2676        editor.delete_to_next_word_end(
 2677            &DeleteToNextWordEnd {
 2678                ignore_newlines: false,
 2679                ignore_brackets: false,
 2680            },
 2681            window,
 2682            cx,
 2683        );
 2684    });
 2685    cx.assert_editor_state("here is some textˇwith a space");
 2686
 2687    cx.set_state("here is some textˇ    with a space");
 2688    cx.update_editor(|editor, window, cx| {
 2689        editor.delete_to_next_word_end(
 2690            &DeleteToNextWordEnd {
 2691                ignore_newlines: true,
 2692                ignore_brackets: false,
 2693            },
 2694            window,
 2695            cx,
 2696        );
 2697    });
 2698    cx.assert_editor_state("here is some textˇwith a space");
 2699    cx.update_editor(|editor, window, cx| {
 2700        editor.delete_to_previous_word_start(
 2701            &DeleteToPreviousWordStart {
 2702                ignore_newlines: true,
 2703                ignore_brackets: false,
 2704            },
 2705            window,
 2706            cx,
 2707        );
 2708    });
 2709    cx.assert_editor_state("here is some ˇwith a space");
 2710    cx.update_editor(|editor, window, cx| {
 2711        editor.delete_to_previous_word_start(
 2712            &DeleteToPreviousWordStart {
 2713                ignore_newlines: true,
 2714                ignore_brackets: false,
 2715            },
 2716            window,
 2717            cx,
 2718        );
 2719    });
 2720    // Single whitespaces are removed with the word behind them.
 2721    cx.assert_editor_state("here is ˇwith a space");
 2722    cx.update_editor(|editor, window, cx| {
 2723        editor.delete_to_previous_word_start(
 2724            &DeleteToPreviousWordStart {
 2725                ignore_newlines: true,
 2726                ignore_brackets: false,
 2727            },
 2728            window,
 2729            cx,
 2730        );
 2731    });
 2732    cx.assert_editor_state("here ˇwith a space");
 2733    cx.update_editor(|editor, window, cx| {
 2734        editor.delete_to_previous_word_start(
 2735            &DeleteToPreviousWordStart {
 2736                ignore_newlines: true,
 2737                ignore_brackets: false,
 2738            },
 2739            window,
 2740            cx,
 2741        );
 2742    });
 2743    cx.assert_editor_state("ˇwith a space");
 2744    cx.update_editor(|editor, window, cx| {
 2745        editor.delete_to_previous_word_start(
 2746            &DeleteToPreviousWordStart {
 2747                ignore_newlines: true,
 2748                ignore_brackets: false,
 2749            },
 2750            window,
 2751            cx,
 2752        );
 2753    });
 2754    cx.assert_editor_state("ˇwith a space");
 2755    cx.update_editor(|editor, window, cx| {
 2756        editor.delete_to_next_word_end(
 2757            &DeleteToNextWordEnd {
 2758                ignore_newlines: true,
 2759                ignore_brackets: false,
 2760            },
 2761            window,
 2762            cx,
 2763        );
 2764    });
 2765    // Same happens in the other direction.
 2766    cx.assert_editor_state("ˇ a space");
 2767    cx.update_editor(|editor, window, cx| {
 2768        editor.delete_to_next_word_end(
 2769            &DeleteToNextWordEnd {
 2770                ignore_newlines: true,
 2771                ignore_brackets: false,
 2772            },
 2773            window,
 2774            cx,
 2775        );
 2776    });
 2777    cx.assert_editor_state("ˇ space");
 2778    cx.update_editor(|editor, window, cx| {
 2779        editor.delete_to_next_word_end(
 2780            &DeleteToNextWordEnd {
 2781                ignore_newlines: true,
 2782                ignore_brackets: false,
 2783            },
 2784            window,
 2785            cx,
 2786        );
 2787    });
 2788    cx.assert_editor_state("ˇ");
 2789    cx.update_editor(|editor, window, cx| {
 2790        editor.delete_to_next_word_end(
 2791            &DeleteToNextWordEnd {
 2792                ignore_newlines: true,
 2793                ignore_brackets: false,
 2794            },
 2795            window,
 2796            cx,
 2797        );
 2798    });
 2799    cx.assert_editor_state("ˇ");
 2800    cx.update_editor(|editor, window, cx| {
 2801        editor.delete_to_previous_word_start(
 2802            &DeleteToPreviousWordStart {
 2803                ignore_newlines: true,
 2804                ignore_brackets: false,
 2805            },
 2806            window,
 2807            cx,
 2808        );
 2809    });
 2810    cx.assert_editor_state("ˇ");
 2811}
 2812
 2813#[gpui::test]
 2814async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2815    init_test(cx, |_| {});
 2816
 2817    let language = Arc::new(
 2818        Language::new(
 2819            LanguageConfig {
 2820                brackets: BracketPairConfig {
 2821                    pairs: vec![
 2822                        BracketPair {
 2823                            start: "\"".to_string(),
 2824                            end: "\"".to_string(),
 2825                            close: true,
 2826                            surround: true,
 2827                            newline: false,
 2828                        },
 2829                        BracketPair {
 2830                            start: "(".to_string(),
 2831                            end: ")".to_string(),
 2832                            close: true,
 2833                            surround: true,
 2834                            newline: true,
 2835                        },
 2836                    ],
 2837                    ..BracketPairConfig::default()
 2838                },
 2839                ..LanguageConfig::default()
 2840            },
 2841            Some(tree_sitter_rust::LANGUAGE.into()),
 2842        )
 2843        .with_brackets_query(
 2844            r#"
 2845                ("(" @open ")" @close)
 2846                ("\"" @open "\"" @close)
 2847            "#,
 2848        )
 2849        .unwrap(),
 2850    );
 2851
 2852    let mut cx = EditorTestContext::new(cx).await;
 2853    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2854
 2855    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2856    cx.update_editor(|editor, window, cx| {
 2857        editor.delete_to_previous_word_start(
 2858            &DeleteToPreviousWordStart {
 2859                ignore_newlines: true,
 2860                ignore_brackets: false,
 2861            },
 2862            window,
 2863            cx,
 2864        );
 2865    });
 2866    // Deletion stops before brackets if asked to not ignore them.
 2867    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2868    cx.update_editor(|editor, window, cx| {
 2869        editor.delete_to_previous_word_start(
 2870            &DeleteToPreviousWordStart {
 2871                ignore_newlines: true,
 2872                ignore_brackets: false,
 2873            },
 2874            window,
 2875            cx,
 2876        );
 2877    });
 2878    // Deletion has to remove a single bracket and then stop again.
 2879    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2880
 2881    cx.update_editor(|editor, window, cx| {
 2882        editor.delete_to_previous_word_start(
 2883            &DeleteToPreviousWordStart {
 2884                ignore_newlines: true,
 2885                ignore_brackets: false,
 2886            },
 2887            window,
 2888            cx,
 2889        );
 2890    });
 2891    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2892
 2893    cx.update_editor(|editor, window, cx| {
 2894        editor.delete_to_previous_word_start(
 2895            &DeleteToPreviousWordStart {
 2896                ignore_newlines: true,
 2897                ignore_brackets: false,
 2898            },
 2899            window,
 2900            cx,
 2901        );
 2902    });
 2903    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2904
 2905    cx.update_editor(|editor, window, cx| {
 2906        editor.delete_to_previous_word_start(
 2907            &DeleteToPreviousWordStart {
 2908                ignore_newlines: true,
 2909                ignore_brackets: false,
 2910            },
 2911            window,
 2912            cx,
 2913        );
 2914    });
 2915    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2916
 2917    cx.update_editor(|editor, window, cx| {
 2918        editor.delete_to_next_word_end(
 2919            &DeleteToNextWordEnd {
 2920                ignore_newlines: true,
 2921                ignore_brackets: false,
 2922            },
 2923            window,
 2924            cx,
 2925        );
 2926    });
 2927    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2928    cx.assert_editor_state(r#"ˇ");"#);
 2929
 2930    cx.update_editor(|editor, window, cx| {
 2931        editor.delete_to_next_word_end(
 2932            &DeleteToNextWordEnd {
 2933                ignore_newlines: true,
 2934                ignore_brackets: false,
 2935            },
 2936            window,
 2937            cx,
 2938        );
 2939    });
 2940    cx.assert_editor_state(r#"ˇ"#);
 2941
 2942    cx.update_editor(|editor, window, cx| {
 2943        editor.delete_to_next_word_end(
 2944            &DeleteToNextWordEnd {
 2945                ignore_newlines: true,
 2946                ignore_brackets: false,
 2947            },
 2948            window,
 2949            cx,
 2950        );
 2951    });
 2952    cx.assert_editor_state(r#"ˇ"#);
 2953
 2954    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2955    cx.update_editor(|editor, window, cx| {
 2956        editor.delete_to_previous_word_start(
 2957            &DeleteToPreviousWordStart {
 2958                ignore_newlines: true,
 2959                ignore_brackets: true,
 2960            },
 2961            window,
 2962            cx,
 2963        );
 2964    });
 2965    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2966}
 2967
 2968#[gpui::test]
 2969fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2970    init_test(cx, |_| {});
 2971
 2972    let editor = cx.add_window(|window, cx| {
 2973        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2974        build_editor(buffer, window, cx)
 2975    });
 2976    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2977        ignore_newlines: false,
 2978        ignore_brackets: false,
 2979    };
 2980    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2981        ignore_newlines: true,
 2982        ignore_brackets: false,
 2983    };
 2984
 2985    _ = editor.update(cx, |editor, window, cx| {
 2986        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2987            s.select_display_ranges([
 2988                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2989            ])
 2990        });
 2991        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2992        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2993        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2994        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2995        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2996        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2997        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2998        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2999        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3000        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3001        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3002        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3003    });
 3004}
 3005
 3006#[gpui::test]
 3007fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3008    init_test(cx, |_| {});
 3009
 3010    let editor = cx.add_window(|window, cx| {
 3011        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3012        build_editor(buffer, window, cx)
 3013    });
 3014    let del_to_next_word_end = DeleteToNextWordEnd {
 3015        ignore_newlines: false,
 3016        ignore_brackets: false,
 3017    };
 3018    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3019        ignore_newlines: true,
 3020        ignore_brackets: false,
 3021    };
 3022
 3023    _ = editor.update(cx, |editor, window, cx| {
 3024        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3025            s.select_display_ranges([
 3026                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3027            ])
 3028        });
 3029        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3030        assert_eq!(
 3031            editor.buffer.read(cx).read(cx).text(),
 3032            "one\n   two\nthree\n   four"
 3033        );
 3034        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3035        assert_eq!(
 3036            editor.buffer.read(cx).read(cx).text(),
 3037            "\n   two\nthree\n   four"
 3038        );
 3039        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3040        assert_eq!(
 3041            editor.buffer.read(cx).read(cx).text(),
 3042            "two\nthree\n   four"
 3043        );
 3044        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3045        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3046        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3047        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3048        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3049        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3050        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3051        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3052    });
 3053}
 3054
 3055#[gpui::test]
 3056fn test_newline(cx: &mut TestAppContext) {
 3057    init_test(cx, |_| {});
 3058
 3059    let editor = cx.add_window(|window, cx| {
 3060        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3061        build_editor(buffer, window, cx)
 3062    });
 3063
 3064    _ = editor.update(cx, |editor, window, cx| {
 3065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3066            s.select_display_ranges([
 3067                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3068                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3069                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3070            ])
 3071        });
 3072
 3073        editor.newline(&Newline, window, cx);
 3074        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3075    });
 3076}
 3077
 3078#[gpui::test]
 3079async fn test_newline_yaml(cx: &mut TestAppContext) {
 3080    init_test(cx, |_| {});
 3081
 3082    let mut cx = EditorTestContext::new(cx).await;
 3083    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3084    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3085
 3086    // Object (between 2 fields)
 3087    cx.set_state(indoc! {"
 3088    test:ˇ
 3089    hello: bye"});
 3090    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3091    cx.assert_editor_state(indoc! {"
 3092    test:
 3093        ˇ
 3094    hello: bye"});
 3095
 3096    // Object (first and single line)
 3097    cx.set_state(indoc! {"
 3098    test:ˇ"});
 3099    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3100    cx.assert_editor_state(indoc! {"
 3101    test:
 3102        ˇ"});
 3103
 3104    // Array with objects (after first element)
 3105    cx.set_state(indoc! {"
 3106    test:
 3107        - foo: barˇ"});
 3108    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3109    cx.assert_editor_state(indoc! {"
 3110    test:
 3111        - foo: bar
 3112        ˇ"});
 3113
 3114    // Array with objects and comment
 3115    cx.set_state(indoc! {"
 3116    test:
 3117        - foo: bar
 3118        - bar: # testˇ"});
 3119    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3120    cx.assert_editor_state(indoc! {"
 3121    test:
 3122        - foo: bar
 3123        - bar: # test
 3124            ˇ"});
 3125
 3126    // Array with objects (after second element)
 3127    cx.set_state(indoc! {"
 3128    test:
 3129        - foo: bar
 3130        - bar: fooˇ"});
 3131    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3132    cx.assert_editor_state(indoc! {"
 3133    test:
 3134        - foo: bar
 3135        - bar: foo
 3136        ˇ"});
 3137
 3138    // Array with strings (after first element)
 3139    cx.set_state(indoc! {"
 3140    test:
 3141        - fooˇ"});
 3142    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3143    cx.assert_editor_state(indoc! {"
 3144    test:
 3145        - foo
 3146        ˇ"});
 3147}
 3148
 3149#[gpui::test]
 3150fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3151    init_test(cx, |_| {});
 3152
 3153    let editor = cx.add_window(|window, cx| {
 3154        let buffer = MultiBuffer::build_simple(
 3155            "
 3156                a
 3157                b(
 3158                    X
 3159                )
 3160                c(
 3161                    X
 3162                )
 3163            "
 3164            .unindent()
 3165            .as_str(),
 3166            cx,
 3167        );
 3168        let mut editor = build_editor(buffer, window, cx);
 3169        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3170            s.select_ranges([
 3171                Point::new(2, 4)..Point::new(2, 5),
 3172                Point::new(5, 4)..Point::new(5, 5),
 3173            ])
 3174        });
 3175        editor
 3176    });
 3177
 3178    _ = editor.update(cx, |editor, window, cx| {
 3179        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3180        editor.buffer.update(cx, |buffer, cx| {
 3181            buffer.edit(
 3182                [
 3183                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3184                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3185                ],
 3186                None,
 3187                cx,
 3188            );
 3189            assert_eq!(
 3190                buffer.read(cx).text(),
 3191                "
 3192                    a
 3193                    b()
 3194                    c()
 3195                "
 3196                .unindent()
 3197            );
 3198        });
 3199        assert_eq!(
 3200            editor.selections.ranges(&editor.display_snapshot(cx)),
 3201            &[
 3202                Point::new(1, 2)..Point::new(1, 2),
 3203                Point::new(2, 2)..Point::new(2, 2),
 3204            ],
 3205        );
 3206
 3207        editor.newline(&Newline, window, cx);
 3208        assert_eq!(
 3209            editor.text(cx),
 3210            "
 3211                a
 3212                b(
 3213                )
 3214                c(
 3215                )
 3216            "
 3217            .unindent()
 3218        );
 3219
 3220        // The selections are moved after the inserted newlines
 3221        assert_eq!(
 3222            editor.selections.ranges(&editor.display_snapshot(cx)),
 3223            &[
 3224                Point::new(2, 0)..Point::new(2, 0),
 3225                Point::new(4, 0)..Point::new(4, 0),
 3226            ],
 3227        );
 3228    });
 3229}
 3230
 3231#[gpui::test]
 3232async fn test_newline_above(cx: &mut TestAppContext) {
 3233    init_test(cx, |settings| {
 3234        settings.defaults.tab_size = NonZeroU32::new(4)
 3235    });
 3236
 3237    let language = Arc::new(
 3238        Language::new(
 3239            LanguageConfig::default(),
 3240            Some(tree_sitter_rust::LANGUAGE.into()),
 3241        )
 3242        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3243        .unwrap(),
 3244    );
 3245
 3246    let mut cx = EditorTestContext::new(cx).await;
 3247    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3248    cx.set_state(indoc! {"
 3249        const a: ˇA = (
 3250 3251                «const_functionˇ»(ˇ),
 3252                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3253 3254        ˇ);ˇ
 3255    "});
 3256
 3257    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3258    cx.assert_editor_state(indoc! {"
 3259        ˇ
 3260        const a: A = (
 3261            ˇ
 3262            (
 3263                ˇ
 3264                ˇ
 3265                const_function(),
 3266                ˇ
 3267                ˇ
 3268                ˇ
 3269                ˇ
 3270                something_else,
 3271                ˇ
 3272            )
 3273            ˇ
 3274            ˇ
 3275        );
 3276    "});
 3277}
 3278
 3279#[gpui::test]
 3280async fn test_newline_below(cx: &mut TestAppContext) {
 3281    init_test(cx, |settings| {
 3282        settings.defaults.tab_size = NonZeroU32::new(4)
 3283    });
 3284
 3285    let language = Arc::new(
 3286        Language::new(
 3287            LanguageConfig::default(),
 3288            Some(tree_sitter_rust::LANGUAGE.into()),
 3289        )
 3290        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3291        .unwrap(),
 3292    );
 3293
 3294    let mut cx = EditorTestContext::new(cx).await;
 3295    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3296    cx.set_state(indoc! {"
 3297        const a: ˇA = (
 3298 3299                «const_functionˇ»(ˇ),
 3300                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3301 3302        ˇ);ˇ
 3303    "});
 3304
 3305    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3306    cx.assert_editor_state(indoc! {"
 3307        const a: A = (
 3308            ˇ
 3309            (
 3310                ˇ
 3311                const_function(),
 3312                ˇ
 3313                ˇ
 3314                something_else,
 3315                ˇ
 3316                ˇ
 3317                ˇ
 3318                ˇ
 3319            )
 3320            ˇ
 3321        );
 3322        ˇ
 3323        ˇ
 3324    "});
 3325}
 3326
 3327#[gpui::test]
 3328async fn test_newline_comments(cx: &mut TestAppContext) {
 3329    init_test(cx, |settings| {
 3330        settings.defaults.tab_size = NonZeroU32::new(4)
 3331    });
 3332
 3333    let language = Arc::new(Language::new(
 3334        LanguageConfig {
 3335            line_comments: vec!["// ".into()],
 3336            ..LanguageConfig::default()
 3337        },
 3338        None,
 3339    ));
 3340    {
 3341        let mut cx = EditorTestContext::new(cx).await;
 3342        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3343        cx.set_state(indoc! {"
 3344        // Fooˇ
 3345    "});
 3346
 3347        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3348        cx.assert_editor_state(indoc! {"
 3349        // Foo
 3350        // ˇ
 3351    "});
 3352        // Ensure that we add comment prefix when existing line contains space
 3353        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3354        cx.assert_editor_state(
 3355            indoc! {"
 3356        // Foo
 3357        //s
 3358        // ˇ
 3359    "}
 3360            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3361            .as_str(),
 3362        );
 3363        // Ensure that we add comment prefix when existing line does not contain space
 3364        cx.set_state(indoc! {"
 3365        // Foo
 3366        //ˇ
 3367    "});
 3368        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3369        cx.assert_editor_state(indoc! {"
 3370        // Foo
 3371        //
 3372        // ˇ
 3373    "});
 3374        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3375        cx.set_state(indoc! {"
 3376        ˇ// Foo
 3377    "});
 3378        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3379        cx.assert_editor_state(indoc! {"
 3380
 3381        ˇ// Foo
 3382    "});
 3383    }
 3384    // Ensure that comment continuations can be disabled.
 3385    update_test_language_settings(cx, |settings| {
 3386        settings.defaults.extend_comment_on_newline = Some(false);
 3387    });
 3388    let mut cx = EditorTestContext::new(cx).await;
 3389    cx.set_state(indoc! {"
 3390        // Fooˇ
 3391    "});
 3392    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3393    cx.assert_editor_state(indoc! {"
 3394        // Foo
 3395        ˇ
 3396    "});
 3397}
 3398
 3399#[gpui::test]
 3400async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3401    init_test(cx, |settings| {
 3402        settings.defaults.tab_size = NonZeroU32::new(4)
 3403    });
 3404
 3405    let language = Arc::new(Language::new(
 3406        LanguageConfig {
 3407            line_comments: vec!["// ".into(), "/// ".into()],
 3408            ..LanguageConfig::default()
 3409        },
 3410        None,
 3411    ));
 3412    {
 3413        let mut cx = EditorTestContext::new(cx).await;
 3414        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3415        cx.set_state(indoc! {"
 3416        //ˇ
 3417    "});
 3418        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3419        cx.assert_editor_state(indoc! {"
 3420        //
 3421        // ˇ
 3422    "});
 3423
 3424        cx.set_state(indoc! {"
 3425        ///ˇ
 3426    "});
 3427        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3428        cx.assert_editor_state(indoc! {"
 3429        ///
 3430        /// ˇ
 3431    "});
 3432    }
 3433}
 3434
 3435#[gpui::test]
 3436async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3437    init_test(cx, |settings| {
 3438        settings.defaults.tab_size = NonZeroU32::new(4)
 3439    });
 3440
 3441    let language = Arc::new(
 3442        Language::new(
 3443            LanguageConfig {
 3444                documentation_comment: Some(language::BlockCommentConfig {
 3445                    start: "/**".into(),
 3446                    end: "*/".into(),
 3447                    prefix: "* ".into(),
 3448                    tab_size: 1,
 3449                }),
 3450
 3451                ..LanguageConfig::default()
 3452            },
 3453            Some(tree_sitter_rust::LANGUAGE.into()),
 3454        )
 3455        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3456        .unwrap(),
 3457    );
 3458
 3459    {
 3460        let mut cx = EditorTestContext::new(cx).await;
 3461        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3462        cx.set_state(indoc! {"
 3463        /**ˇ
 3464    "});
 3465
 3466        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3467        cx.assert_editor_state(indoc! {"
 3468        /**
 3469         * ˇ
 3470    "});
 3471        // Ensure that if cursor is before the comment start,
 3472        // we do not actually insert a comment prefix.
 3473        cx.set_state(indoc! {"
 3474        ˇ/**
 3475    "});
 3476        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3477        cx.assert_editor_state(indoc! {"
 3478
 3479        ˇ/**
 3480    "});
 3481        // Ensure that if cursor is between it doesn't add comment prefix.
 3482        cx.set_state(indoc! {"
 3483        /*ˇ*
 3484    "});
 3485        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3486        cx.assert_editor_state(indoc! {"
 3487        /*
 3488        ˇ*
 3489    "});
 3490        // Ensure that if suffix exists on same line after cursor it adds new line.
 3491        cx.set_state(indoc! {"
 3492        /**ˇ*/
 3493    "});
 3494        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3495        cx.assert_editor_state(indoc! {"
 3496        /**
 3497         * ˇ
 3498         */
 3499    "});
 3500        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3501        cx.set_state(indoc! {"
 3502        /**ˇ */
 3503    "});
 3504        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3505        cx.assert_editor_state(indoc! {"
 3506        /**
 3507         * ˇ
 3508         */
 3509    "});
 3510        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3511        cx.set_state(indoc! {"
 3512        /** ˇ*/
 3513    "});
 3514        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3515        cx.assert_editor_state(
 3516            indoc! {"
 3517        /**s
 3518         * ˇ
 3519         */
 3520    "}
 3521            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3522            .as_str(),
 3523        );
 3524        // Ensure that delimiter space is preserved when newline on already
 3525        // spaced delimiter.
 3526        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3527        cx.assert_editor_state(
 3528            indoc! {"
 3529        /**s
 3530         *s
 3531         * ˇ
 3532         */
 3533    "}
 3534            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3535            .as_str(),
 3536        );
 3537        // Ensure that delimiter space is preserved when space is not
 3538        // on existing delimiter.
 3539        cx.set_state(indoc! {"
 3540        /**
 3541 3542         */
 3543    "});
 3544        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3545        cx.assert_editor_state(indoc! {"
 3546        /**
 3547         *
 3548         * ˇ
 3549         */
 3550    "});
 3551        // Ensure that if suffix exists on same line after cursor it
 3552        // doesn't add extra new line if prefix is not on same line.
 3553        cx.set_state(indoc! {"
 3554        /**
 3555        ˇ*/
 3556    "});
 3557        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3558        cx.assert_editor_state(indoc! {"
 3559        /**
 3560
 3561        ˇ*/
 3562    "});
 3563        // Ensure that it detects suffix after existing prefix.
 3564        cx.set_state(indoc! {"
 3565        /**ˇ/
 3566    "});
 3567        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3568        cx.assert_editor_state(indoc! {"
 3569        /**
 3570        ˇ/
 3571    "});
 3572        // Ensure that if suffix exists on same line before
 3573        // cursor it does not add comment prefix.
 3574        cx.set_state(indoc! {"
 3575        /** */ˇ
 3576    "});
 3577        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3578        cx.assert_editor_state(indoc! {"
 3579        /** */
 3580        ˇ
 3581    "});
 3582        // Ensure that if suffix exists on same line before
 3583        // cursor it does not add comment prefix.
 3584        cx.set_state(indoc! {"
 3585        /**
 3586         *
 3587         */ˇ
 3588    "});
 3589        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3590        cx.assert_editor_state(indoc! {"
 3591        /**
 3592         *
 3593         */
 3594         ˇ
 3595    "});
 3596
 3597        // Ensure that inline comment followed by code
 3598        // doesn't add comment prefix on newline
 3599        cx.set_state(indoc! {"
 3600        /** */ textˇ
 3601    "});
 3602        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3603        cx.assert_editor_state(indoc! {"
 3604        /** */ text
 3605        ˇ
 3606    "});
 3607
 3608        // Ensure that text after comment end tag
 3609        // doesn't add comment prefix on newline
 3610        cx.set_state(indoc! {"
 3611        /**
 3612         *
 3613         */ˇtext
 3614    "});
 3615        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3616        cx.assert_editor_state(indoc! {"
 3617        /**
 3618         *
 3619         */
 3620         ˇtext
 3621    "});
 3622
 3623        // Ensure if not comment block it doesn't
 3624        // add comment prefix on newline
 3625        cx.set_state(indoc! {"
 3626        * textˇ
 3627    "});
 3628        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3629        cx.assert_editor_state(indoc! {"
 3630        * text
 3631        ˇ
 3632    "});
 3633    }
 3634    // Ensure that comment continuations can be disabled.
 3635    update_test_language_settings(cx, |settings| {
 3636        settings.defaults.extend_comment_on_newline = Some(false);
 3637    });
 3638    let mut cx = EditorTestContext::new(cx).await;
 3639    cx.set_state(indoc! {"
 3640        /**ˇ
 3641    "});
 3642    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3643    cx.assert_editor_state(indoc! {"
 3644        /**
 3645        ˇ
 3646    "});
 3647}
 3648
 3649#[gpui::test]
 3650async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3651    init_test(cx, |settings| {
 3652        settings.defaults.tab_size = NonZeroU32::new(4)
 3653    });
 3654
 3655    let lua_language = Arc::new(Language::new(
 3656        LanguageConfig {
 3657            line_comments: vec!["--".into()],
 3658            block_comment: Some(language::BlockCommentConfig {
 3659                start: "--[[".into(),
 3660                prefix: "".into(),
 3661                end: "]]".into(),
 3662                tab_size: 0,
 3663            }),
 3664            ..LanguageConfig::default()
 3665        },
 3666        None,
 3667    ));
 3668
 3669    let mut cx = EditorTestContext::new(cx).await;
 3670    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3671
 3672    // Line with line comment should extend
 3673    cx.set_state(indoc! {"
 3674        --ˇ
 3675    "});
 3676    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3677    cx.assert_editor_state(indoc! {"
 3678        --
 3679        --ˇ
 3680    "});
 3681
 3682    // Line with block comment that matches line comment should not extend
 3683    cx.set_state(indoc! {"
 3684        --[[ˇ
 3685    "});
 3686    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3687    cx.assert_editor_state(indoc! {"
 3688        --[[
 3689        ˇ
 3690    "});
 3691}
 3692
 3693#[gpui::test]
 3694fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3695    init_test(cx, |_| {});
 3696
 3697    let editor = cx.add_window(|window, cx| {
 3698        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3699        let mut editor = build_editor(buffer, window, cx);
 3700        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3701            s.select_ranges([3..4, 11..12, 19..20])
 3702        });
 3703        editor
 3704    });
 3705
 3706    _ = editor.update(cx, |editor, window, cx| {
 3707        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3708        editor.buffer.update(cx, |buffer, cx| {
 3709            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3710            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3711        });
 3712        assert_eq!(
 3713            editor.selections.ranges(&editor.display_snapshot(cx)),
 3714            &[2..2, 7..7, 12..12],
 3715        );
 3716
 3717        editor.insert("Z", window, cx);
 3718        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3719
 3720        // The selections are moved after the inserted characters
 3721        assert_eq!(
 3722            editor.selections.ranges(&editor.display_snapshot(cx)),
 3723            &[3..3, 9..9, 15..15],
 3724        );
 3725    });
 3726}
 3727
 3728#[gpui::test]
 3729async fn test_tab(cx: &mut TestAppContext) {
 3730    init_test(cx, |settings| {
 3731        settings.defaults.tab_size = NonZeroU32::new(3)
 3732    });
 3733
 3734    let mut cx = EditorTestContext::new(cx).await;
 3735    cx.set_state(indoc! {"
 3736        ˇabˇc
 3737        ˇ🏀ˇ🏀ˇefg
 3738 3739    "});
 3740    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3741    cx.assert_editor_state(indoc! {"
 3742           ˇab ˇc
 3743           ˇ🏀  ˇ🏀  ˇefg
 3744        d  ˇ
 3745    "});
 3746
 3747    cx.set_state(indoc! {"
 3748        a
 3749        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3750    "});
 3751    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3752    cx.assert_editor_state(indoc! {"
 3753        a
 3754           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3755    "});
 3756}
 3757
 3758#[gpui::test]
 3759async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3760    init_test(cx, |_| {});
 3761
 3762    let mut cx = EditorTestContext::new(cx).await;
 3763    let language = Arc::new(
 3764        Language::new(
 3765            LanguageConfig::default(),
 3766            Some(tree_sitter_rust::LANGUAGE.into()),
 3767        )
 3768        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3769        .unwrap(),
 3770    );
 3771    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3772
 3773    // test when all cursors are not at suggested indent
 3774    // then simply move to their suggested indent location
 3775    cx.set_state(indoc! {"
 3776        const a: B = (
 3777            c(
 3778        ˇ
 3779        ˇ    )
 3780        );
 3781    "});
 3782    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3783    cx.assert_editor_state(indoc! {"
 3784        const a: B = (
 3785            c(
 3786                ˇ
 3787            ˇ)
 3788        );
 3789    "});
 3790
 3791    // test cursor already at suggested indent not moving when
 3792    // other cursors are yet to reach their suggested indents
 3793    cx.set_state(indoc! {"
 3794        ˇ
 3795        const a: B = (
 3796            c(
 3797                d(
 3798        ˇ
 3799                )
 3800        ˇ
 3801        ˇ    )
 3802        );
 3803    "});
 3804    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3805    cx.assert_editor_state(indoc! {"
 3806        ˇ
 3807        const a: B = (
 3808            c(
 3809                d(
 3810                    ˇ
 3811                )
 3812                ˇ
 3813            ˇ)
 3814        );
 3815    "});
 3816    // test when all cursors are at suggested indent then tab is inserted
 3817    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3818    cx.assert_editor_state(indoc! {"
 3819            ˇ
 3820        const a: B = (
 3821            c(
 3822                d(
 3823                        ˇ
 3824                )
 3825                    ˇ
 3826                ˇ)
 3827        );
 3828    "});
 3829
 3830    // test when current indent is less than suggested indent,
 3831    // we adjust line to match suggested indent and move cursor to it
 3832    //
 3833    // when no other cursor is at word boundary, all of them should move
 3834    cx.set_state(indoc! {"
 3835        const a: B = (
 3836            c(
 3837                d(
 3838        ˇ
 3839        ˇ   )
 3840        ˇ   )
 3841        );
 3842    "});
 3843    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3844    cx.assert_editor_state(indoc! {"
 3845        const a: B = (
 3846            c(
 3847                d(
 3848                    ˇ
 3849                ˇ)
 3850            ˇ)
 3851        );
 3852    "});
 3853
 3854    // test when current indent is less than suggested indent,
 3855    // we adjust line to match suggested indent and move cursor to it
 3856    //
 3857    // when some other cursor is at word boundary, it should not move
 3858    cx.set_state(indoc! {"
 3859        const a: B = (
 3860            c(
 3861                d(
 3862        ˇ
 3863        ˇ   )
 3864           ˇ)
 3865        );
 3866    "});
 3867    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3868    cx.assert_editor_state(indoc! {"
 3869        const a: B = (
 3870            c(
 3871                d(
 3872                    ˇ
 3873                ˇ)
 3874            ˇ)
 3875        );
 3876    "});
 3877
 3878    // test when current indent is more than suggested indent,
 3879    // we just move cursor to current indent instead of suggested indent
 3880    //
 3881    // when no other cursor is at word boundary, all of them should move
 3882    cx.set_state(indoc! {"
 3883        const a: B = (
 3884            c(
 3885                d(
 3886        ˇ
 3887        ˇ                )
 3888        ˇ   )
 3889        );
 3890    "});
 3891    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3892    cx.assert_editor_state(indoc! {"
 3893        const a: B = (
 3894            c(
 3895                d(
 3896                    ˇ
 3897                        ˇ)
 3898            ˇ)
 3899        );
 3900    "});
 3901    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3902    cx.assert_editor_state(indoc! {"
 3903        const a: B = (
 3904            c(
 3905                d(
 3906                        ˇ
 3907                            ˇ)
 3908                ˇ)
 3909        );
 3910    "});
 3911
 3912    // test when current indent is more than suggested indent,
 3913    // we just move cursor to current indent instead of suggested indent
 3914    //
 3915    // when some other cursor is at word boundary, it doesn't move
 3916    cx.set_state(indoc! {"
 3917        const a: B = (
 3918            c(
 3919                d(
 3920        ˇ
 3921        ˇ                )
 3922            ˇ)
 3923        );
 3924    "});
 3925    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3926    cx.assert_editor_state(indoc! {"
 3927        const a: B = (
 3928            c(
 3929                d(
 3930                    ˇ
 3931                        ˇ)
 3932            ˇ)
 3933        );
 3934    "});
 3935
 3936    // handle auto-indent when there are multiple cursors on the same line
 3937    cx.set_state(indoc! {"
 3938        const a: B = (
 3939            c(
 3940        ˇ    ˇ
 3941        ˇ    )
 3942        );
 3943    "});
 3944    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3945    cx.assert_editor_state(indoc! {"
 3946        const a: B = (
 3947            c(
 3948                ˇ
 3949            ˇ)
 3950        );
 3951    "});
 3952}
 3953
 3954#[gpui::test]
 3955async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3956    init_test(cx, |settings| {
 3957        settings.defaults.tab_size = NonZeroU32::new(3)
 3958    });
 3959
 3960    let mut cx = EditorTestContext::new(cx).await;
 3961    cx.set_state(indoc! {"
 3962         ˇ
 3963        \t ˇ
 3964        \t  ˇ
 3965        \t   ˇ
 3966         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3967    "});
 3968
 3969    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3970    cx.assert_editor_state(indoc! {"
 3971           ˇ
 3972        \t   ˇ
 3973        \t   ˇ
 3974        \t      ˇ
 3975         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3976    "});
 3977}
 3978
 3979#[gpui::test]
 3980async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3981    init_test(cx, |settings| {
 3982        settings.defaults.tab_size = NonZeroU32::new(4)
 3983    });
 3984
 3985    let language = Arc::new(
 3986        Language::new(
 3987            LanguageConfig::default(),
 3988            Some(tree_sitter_rust::LANGUAGE.into()),
 3989        )
 3990        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3991        .unwrap(),
 3992    );
 3993
 3994    let mut cx = EditorTestContext::new(cx).await;
 3995    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3996    cx.set_state(indoc! {"
 3997        fn a() {
 3998            if b {
 3999        \t ˇc
 4000            }
 4001        }
 4002    "});
 4003
 4004    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4005    cx.assert_editor_state(indoc! {"
 4006        fn a() {
 4007            if b {
 4008                ˇc
 4009            }
 4010        }
 4011    "});
 4012}
 4013
 4014#[gpui::test]
 4015async fn test_indent_outdent(cx: &mut TestAppContext) {
 4016    init_test(cx, |settings| {
 4017        settings.defaults.tab_size = NonZeroU32::new(4);
 4018    });
 4019
 4020    let mut cx = EditorTestContext::new(cx).await;
 4021
 4022    cx.set_state(indoc! {"
 4023          «oneˇ» «twoˇ»
 4024        three
 4025         four
 4026    "});
 4027    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4028    cx.assert_editor_state(indoc! {"
 4029            «oneˇ» «twoˇ»
 4030        three
 4031         four
 4032    "});
 4033
 4034    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4035    cx.assert_editor_state(indoc! {"
 4036        «oneˇ» «twoˇ»
 4037        three
 4038         four
 4039    "});
 4040
 4041    // select across line ending
 4042    cx.set_state(indoc! {"
 4043        one two
 4044        t«hree
 4045        ˇ» four
 4046    "});
 4047    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4048    cx.assert_editor_state(indoc! {"
 4049        one two
 4050            t«hree
 4051        ˇ» four
 4052    "});
 4053
 4054    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4055    cx.assert_editor_state(indoc! {"
 4056        one two
 4057        t«hree
 4058        ˇ» four
 4059    "});
 4060
 4061    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4062    cx.set_state(indoc! {"
 4063        one two
 4064        ˇthree
 4065            four
 4066    "});
 4067    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4068    cx.assert_editor_state(indoc! {"
 4069        one two
 4070            ˇthree
 4071            four
 4072    "});
 4073
 4074    cx.set_state(indoc! {"
 4075        one two
 4076        ˇ    three
 4077            four
 4078    "});
 4079    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4080    cx.assert_editor_state(indoc! {"
 4081        one two
 4082        ˇthree
 4083            four
 4084    "});
 4085}
 4086
 4087#[gpui::test]
 4088async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4089    // This is a regression test for issue #33761
 4090    init_test(cx, |_| {});
 4091
 4092    let mut cx = EditorTestContext::new(cx).await;
 4093    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4094    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4095
 4096    cx.set_state(
 4097        r#"ˇ#     ingress:
 4098ˇ#         api:
 4099ˇ#             enabled: false
 4100ˇ#             pathType: Prefix
 4101ˇ#           console:
 4102ˇ#               enabled: false
 4103ˇ#               pathType: Prefix
 4104"#,
 4105    );
 4106
 4107    // Press tab to indent all lines
 4108    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4109
 4110    cx.assert_editor_state(
 4111        r#"    ˇ#     ingress:
 4112    ˇ#         api:
 4113    ˇ#             enabled: false
 4114    ˇ#             pathType: Prefix
 4115    ˇ#           console:
 4116    ˇ#               enabled: false
 4117    ˇ#               pathType: Prefix
 4118"#,
 4119    );
 4120}
 4121
 4122#[gpui::test]
 4123async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4124    // This is a test to make sure our fix for issue #33761 didn't break anything
 4125    init_test(cx, |_| {});
 4126
 4127    let mut cx = EditorTestContext::new(cx).await;
 4128    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4129    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4130
 4131    cx.set_state(
 4132        r#"ˇingress:
 4133ˇ  api:
 4134ˇ    enabled: false
 4135ˇ    pathType: Prefix
 4136"#,
 4137    );
 4138
 4139    // Press tab to indent all lines
 4140    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4141
 4142    cx.assert_editor_state(
 4143        r#"ˇingress:
 4144    ˇapi:
 4145        ˇenabled: false
 4146        ˇpathType: Prefix
 4147"#,
 4148    );
 4149}
 4150
 4151#[gpui::test]
 4152async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4153    init_test(cx, |settings| {
 4154        settings.defaults.hard_tabs = Some(true);
 4155    });
 4156
 4157    let mut cx = EditorTestContext::new(cx).await;
 4158
 4159    // select two ranges on one line
 4160    cx.set_state(indoc! {"
 4161        «oneˇ» «twoˇ»
 4162        three
 4163        four
 4164    "});
 4165    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4166    cx.assert_editor_state(indoc! {"
 4167        \t«oneˇ» «twoˇ»
 4168        three
 4169        four
 4170    "});
 4171    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4172    cx.assert_editor_state(indoc! {"
 4173        \t\t«oneˇ» «twoˇ»
 4174        three
 4175        four
 4176    "});
 4177    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4178    cx.assert_editor_state(indoc! {"
 4179        \t«oneˇ» «twoˇ»
 4180        three
 4181        four
 4182    "});
 4183    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4184    cx.assert_editor_state(indoc! {"
 4185        «oneˇ» «twoˇ»
 4186        three
 4187        four
 4188    "});
 4189
 4190    // select across a line ending
 4191    cx.set_state(indoc! {"
 4192        one two
 4193        t«hree
 4194        ˇ»four
 4195    "});
 4196    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4197    cx.assert_editor_state(indoc! {"
 4198        one two
 4199        \tt«hree
 4200        ˇ»four
 4201    "});
 4202    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4203    cx.assert_editor_state(indoc! {"
 4204        one two
 4205        \t\tt«hree
 4206        ˇ»four
 4207    "});
 4208    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4209    cx.assert_editor_state(indoc! {"
 4210        one two
 4211        \tt«hree
 4212        ˇ»four
 4213    "});
 4214    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4215    cx.assert_editor_state(indoc! {"
 4216        one two
 4217        t«hree
 4218        ˇ»four
 4219    "});
 4220
 4221    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4222    cx.set_state(indoc! {"
 4223        one two
 4224        ˇthree
 4225        four
 4226    "});
 4227    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4228    cx.assert_editor_state(indoc! {"
 4229        one two
 4230        ˇthree
 4231        four
 4232    "});
 4233    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4234    cx.assert_editor_state(indoc! {"
 4235        one two
 4236        \tˇthree
 4237        four
 4238    "});
 4239    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4240    cx.assert_editor_state(indoc! {"
 4241        one two
 4242        ˇthree
 4243        four
 4244    "});
 4245}
 4246
 4247#[gpui::test]
 4248fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4249    init_test(cx, |settings| {
 4250        settings.languages.0.extend([
 4251            (
 4252                "TOML".into(),
 4253                LanguageSettingsContent {
 4254                    tab_size: NonZeroU32::new(2),
 4255                    ..Default::default()
 4256                },
 4257            ),
 4258            (
 4259                "Rust".into(),
 4260                LanguageSettingsContent {
 4261                    tab_size: NonZeroU32::new(4),
 4262                    ..Default::default()
 4263                },
 4264            ),
 4265        ]);
 4266    });
 4267
 4268    let toml_language = Arc::new(Language::new(
 4269        LanguageConfig {
 4270            name: "TOML".into(),
 4271            ..Default::default()
 4272        },
 4273        None,
 4274    ));
 4275    let rust_language = Arc::new(Language::new(
 4276        LanguageConfig {
 4277            name: "Rust".into(),
 4278            ..Default::default()
 4279        },
 4280        None,
 4281    ));
 4282
 4283    let toml_buffer =
 4284        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4285    let rust_buffer =
 4286        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4287    let multibuffer = cx.new(|cx| {
 4288        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4289        multibuffer.push_excerpts(
 4290            toml_buffer.clone(),
 4291            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4292            cx,
 4293        );
 4294        multibuffer.push_excerpts(
 4295            rust_buffer.clone(),
 4296            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4297            cx,
 4298        );
 4299        multibuffer
 4300    });
 4301
 4302    cx.add_window(|window, cx| {
 4303        let mut editor = build_editor(multibuffer, window, cx);
 4304
 4305        assert_eq!(
 4306            editor.text(cx),
 4307            indoc! {"
 4308                a = 1
 4309                b = 2
 4310
 4311                const c: usize = 3;
 4312            "}
 4313        );
 4314
 4315        select_ranges(
 4316            &mut editor,
 4317            indoc! {"
 4318                «aˇ» = 1
 4319                b = 2
 4320
 4321                «const c:ˇ» usize = 3;
 4322            "},
 4323            window,
 4324            cx,
 4325        );
 4326
 4327        editor.tab(&Tab, window, cx);
 4328        assert_text_with_selections(
 4329            &mut editor,
 4330            indoc! {"
 4331                  «aˇ» = 1
 4332                b = 2
 4333
 4334                    «const c:ˇ» usize = 3;
 4335            "},
 4336            cx,
 4337        );
 4338        editor.backtab(&Backtab, window, cx);
 4339        assert_text_with_selections(
 4340            &mut editor,
 4341            indoc! {"
 4342                «aˇ» = 1
 4343                b = 2
 4344
 4345                «const c:ˇ» usize = 3;
 4346            "},
 4347            cx,
 4348        );
 4349
 4350        editor
 4351    });
 4352}
 4353
 4354#[gpui::test]
 4355async fn test_backspace(cx: &mut TestAppContext) {
 4356    init_test(cx, |_| {});
 4357
 4358    let mut cx = EditorTestContext::new(cx).await;
 4359
 4360    // Basic backspace
 4361    cx.set_state(indoc! {"
 4362        onˇe two three
 4363        fou«rˇ» five six
 4364        seven «ˇeight nine
 4365        »ten
 4366    "});
 4367    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4368    cx.assert_editor_state(indoc! {"
 4369        oˇe two three
 4370        fouˇ five six
 4371        seven ˇten
 4372    "});
 4373
 4374    // Test backspace inside and around indents
 4375    cx.set_state(indoc! {"
 4376        zero
 4377            ˇone
 4378                ˇtwo
 4379            ˇ ˇ ˇ  three
 4380        ˇ  ˇ  four
 4381    "});
 4382    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4383    cx.assert_editor_state(indoc! {"
 4384        zero
 4385        ˇone
 4386            ˇtwo
 4387        ˇ  threeˇ  four
 4388    "});
 4389}
 4390
 4391#[gpui::test]
 4392async fn test_delete(cx: &mut TestAppContext) {
 4393    init_test(cx, |_| {});
 4394
 4395    let mut cx = EditorTestContext::new(cx).await;
 4396    cx.set_state(indoc! {"
 4397        onˇe two three
 4398        fou«rˇ» five six
 4399        seven «ˇeight nine
 4400        »ten
 4401    "});
 4402    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4403    cx.assert_editor_state(indoc! {"
 4404        onˇ two three
 4405        fouˇ five six
 4406        seven ˇten
 4407    "});
 4408}
 4409
 4410#[gpui::test]
 4411fn test_delete_line(cx: &mut TestAppContext) {
 4412    init_test(cx, |_| {});
 4413
 4414    let editor = cx.add_window(|window, cx| {
 4415        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4416        build_editor(buffer, window, cx)
 4417    });
 4418    _ = editor.update(cx, |editor, window, cx| {
 4419        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4420            s.select_display_ranges([
 4421                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4422                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4423                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4424            ])
 4425        });
 4426        editor.delete_line(&DeleteLine, window, cx);
 4427        assert_eq!(editor.display_text(cx), "ghi");
 4428        assert_eq!(
 4429            display_ranges(editor, cx),
 4430            vec![
 4431                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4432                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4433            ]
 4434        );
 4435    });
 4436
 4437    let editor = cx.add_window(|window, cx| {
 4438        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4439        build_editor(buffer, window, cx)
 4440    });
 4441    _ = editor.update(cx, |editor, window, cx| {
 4442        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4443            s.select_display_ranges([
 4444                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4445            ])
 4446        });
 4447        editor.delete_line(&DeleteLine, window, cx);
 4448        assert_eq!(editor.display_text(cx), "ghi\n");
 4449        assert_eq!(
 4450            display_ranges(editor, cx),
 4451            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4452        );
 4453    });
 4454
 4455    let editor = cx.add_window(|window, cx| {
 4456        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4457        build_editor(buffer, window, cx)
 4458    });
 4459    _ = editor.update(cx, |editor, window, cx| {
 4460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4461            s.select_display_ranges([
 4462                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4463            ])
 4464        });
 4465        editor.delete_line(&DeleteLine, window, cx);
 4466        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4467        assert_eq!(
 4468            display_ranges(editor, cx),
 4469            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4470        );
 4471    });
 4472}
 4473
 4474#[gpui::test]
 4475fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4476    init_test(cx, |_| {});
 4477
 4478    cx.add_window(|window, cx| {
 4479        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4480        let mut editor = build_editor(buffer.clone(), window, cx);
 4481        let buffer = buffer.read(cx).as_singleton().unwrap();
 4482
 4483        assert_eq!(
 4484            editor
 4485                .selections
 4486                .ranges::<Point>(&editor.display_snapshot(cx)),
 4487            &[Point::new(0, 0)..Point::new(0, 0)]
 4488        );
 4489
 4490        // When on single line, replace newline at end by space
 4491        editor.join_lines(&JoinLines, window, cx);
 4492        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4493        assert_eq!(
 4494            editor
 4495                .selections
 4496                .ranges::<Point>(&editor.display_snapshot(cx)),
 4497            &[Point::new(0, 3)..Point::new(0, 3)]
 4498        );
 4499
 4500        // When multiple lines are selected, remove newlines that are spanned by the selection
 4501        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4502            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4503        });
 4504        editor.join_lines(&JoinLines, window, cx);
 4505        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4506        assert_eq!(
 4507            editor
 4508                .selections
 4509                .ranges::<Point>(&editor.display_snapshot(cx)),
 4510            &[Point::new(0, 11)..Point::new(0, 11)]
 4511        );
 4512
 4513        // Undo should be transactional
 4514        editor.undo(&Undo, window, cx);
 4515        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4516        assert_eq!(
 4517            editor
 4518                .selections
 4519                .ranges::<Point>(&editor.display_snapshot(cx)),
 4520            &[Point::new(0, 5)..Point::new(2, 2)]
 4521        );
 4522
 4523        // When joining an empty line don't insert a space
 4524        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4525            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4526        });
 4527        editor.join_lines(&JoinLines, window, cx);
 4528        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4529        assert_eq!(
 4530            editor
 4531                .selections
 4532                .ranges::<Point>(&editor.display_snapshot(cx)),
 4533            [Point::new(2, 3)..Point::new(2, 3)]
 4534        );
 4535
 4536        // We can remove trailing newlines
 4537        editor.join_lines(&JoinLines, window, cx);
 4538        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4539        assert_eq!(
 4540            editor
 4541                .selections
 4542                .ranges::<Point>(&editor.display_snapshot(cx)),
 4543            [Point::new(2, 3)..Point::new(2, 3)]
 4544        );
 4545
 4546        // We don't blow up on the last line
 4547        editor.join_lines(&JoinLines, window, cx);
 4548        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4549        assert_eq!(
 4550            editor
 4551                .selections
 4552                .ranges::<Point>(&editor.display_snapshot(cx)),
 4553            [Point::new(2, 3)..Point::new(2, 3)]
 4554        );
 4555
 4556        // reset to test indentation
 4557        editor.buffer.update(cx, |buffer, cx| {
 4558            buffer.edit(
 4559                [
 4560                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4561                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4562                ],
 4563                None,
 4564                cx,
 4565            )
 4566        });
 4567
 4568        // We remove any leading spaces
 4569        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4571            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4572        });
 4573        editor.join_lines(&JoinLines, window, cx);
 4574        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4575
 4576        // We don't insert a space for a line containing only spaces
 4577        editor.join_lines(&JoinLines, window, cx);
 4578        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4579
 4580        // We ignore any leading tabs
 4581        editor.join_lines(&JoinLines, window, cx);
 4582        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4583
 4584        editor
 4585    });
 4586}
 4587
 4588#[gpui::test]
 4589fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4590    init_test(cx, |_| {});
 4591
 4592    cx.add_window(|window, cx| {
 4593        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4594        let mut editor = build_editor(buffer.clone(), window, cx);
 4595        let buffer = buffer.read(cx).as_singleton().unwrap();
 4596
 4597        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4598            s.select_ranges([
 4599                Point::new(0, 2)..Point::new(1, 1),
 4600                Point::new(1, 2)..Point::new(1, 2),
 4601                Point::new(3, 1)..Point::new(3, 2),
 4602            ])
 4603        });
 4604
 4605        editor.join_lines(&JoinLines, window, cx);
 4606        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4607
 4608        assert_eq!(
 4609            editor
 4610                .selections
 4611                .ranges::<Point>(&editor.display_snapshot(cx)),
 4612            [
 4613                Point::new(0, 7)..Point::new(0, 7),
 4614                Point::new(1, 3)..Point::new(1, 3)
 4615            ]
 4616        );
 4617        editor
 4618    });
 4619}
 4620
 4621#[gpui::test]
 4622async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4623    init_test(cx, |_| {});
 4624
 4625    let mut cx = EditorTestContext::new(cx).await;
 4626
 4627    let diff_base = r#"
 4628        Line 0
 4629        Line 1
 4630        Line 2
 4631        Line 3
 4632        "#
 4633    .unindent();
 4634
 4635    cx.set_state(
 4636        &r#"
 4637        ˇLine 0
 4638        Line 1
 4639        Line 2
 4640        Line 3
 4641        "#
 4642        .unindent(),
 4643    );
 4644
 4645    cx.set_head_text(&diff_base);
 4646    executor.run_until_parked();
 4647
 4648    // Join lines
 4649    cx.update_editor(|editor, window, cx| {
 4650        editor.join_lines(&JoinLines, window, cx);
 4651    });
 4652    executor.run_until_parked();
 4653
 4654    cx.assert_editor_state(
 4655        &r#"
 4656        Line 0ˇ Line 1
 4657        Line 2
 4658        Line 3
 4659        "#
 4660        .unindent(),
 4661    );
 4662    // Join again
 4663    cx.update_editor(|editor, window, cx| {
 4664        editor.join_lines(&JoinLines, window, cx);
 4665    });
 4666    executor.run_until_parked();
 4667
 4668    cx.assert_editor_state(
 4669        &r#"
 4670        Line 0 Line 1ˇ Line 2
 4671        Line 3
 4672        "#
 4673        .unindent(),
 4674    );
 4675}
 4676
 4677#[gpui::test]
 4678async fn test_custom_newlines_cause_no_false_positive_diffs(
 4679    executor: BackgroundExecutor,
 4680    cx: &mut TestAppContext,
 4681) {
 4682    init_test(cx, |_| {});
 4683    let mut cx = EditorTestContext::new(cx).await;
 4684    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4685    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4686    executor.run_until_parked();
 4687
 4688    cx.update_editor(|editor, window, cx| {
 4689        let snapshot = editor.snapshot(window, cx);
 4690        assert_eq!(
 4691            snapshot
 4692                .buffer_snapshot()
 4693                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4694                .collect::<Vec<_>>(),
 4695            Vec::new(),
 4696            "Should not have any diffs for files with custom newlines"
 4697        );
 4698    });
 4699}
 4700
 4701#[gpui::test]
 4702async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4703    init_test(cx, |_| {});
 4704
 4705    let mut cx = EditorTestContext::new(cx).await;
 4706
 4707    // Test sort_lines_case_insensitive()
 4708    cx.set_state(indoc! {"
 4709        «z
 4710        y
 4711        x
 4712        Z
 4713        Y
 4714        Xˇ»
 4715    "});
 4716    cx.update_editor(|e, window, cx| {
 4717        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4718    });
 4719    cx.assert_editor_state(indoc! {"
 4720        «x
 4721        X
 4722        y
 4723        Y
 4724        z
 4725        Zˇ»
 4726    "});
 4727
 4728    // Test sort_lines_by_length()
 4729    //
 4730    // Demonstrates:
 4731    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4732    // - sort is stable
 4733    cx.set_state(indoc! {"
 4734        «123
 4735        æ
 4736        12
 4737 4738        1
 4739        æˇ»
 4740    "});
 4741    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4742    cx.assert_editor_state(indoc! {"
 4743        «æ
 4744 4745        1
 4746        æ
 4747        12
 4748        123ˇ»
 4749    "});
 4750
 4751    // Test reverse_lines()
 4752    cx.set_state(indoc! {"
 4753        «5
 4754        4
 4755        3
 4756        2
 4757        1ˇ»
 4758    "});
 4759    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4760    cx.assert_editor_state(indoc! {"
 4761        «1
 4762        2
 4763        3
 4764        4
 4765        5ˇ»
 4766    "});
 4767
 4768    // Skip testing shuffle_line()
 4769
 4770    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4771    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4772
 4773    // Don't manipulate when cursor is on single line, but expand the selection
 4774    cx.set_state(indoc! {"
 4775        ddˇdd
 4776        ccc
 4777        bb
 4778        a
 4779    "});
 4780    cx.update_editor(|e, window, cx| {
 4781        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4782    });
 4783    cx.assert_editor_state(indoc! {"
 4784        «ddddˇ»
 4785        ccc
 4786        bb
 4787        a
 4788    "});
 4789
 4790    // Basic manipulate case
 4791    // Start selection moves to column 0
 4792    // End of selection shrinks to fit shorter line
 4793    cx.set_state(indoc! {"
 4794        dd«d
 4795        ccc
 4796        bb
 4797        aaaaaˇ»
 4798    "});
 4799    cx.update_editor(|e, window, cx| {
 4800        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4801    });
 4802    cx.assert_editor_state(indoc! {"
 4803        «aaaaa
 4804        bb
 4805        ccc
 4806        dddˇ»
 4807    "});
 4808
 4809    // Manipulate case with newlines
 4810    cx.set_state(indoc! {"
 4811        dd«d
 4812        ccc
 4813
 4814        bb
 4815        aaaaa
 4816
 4817        ˇ»
 4818    "});
 4819    cx.update_editor(|e, window, cx| {
 4820        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4821    });
 4822    cx.assert_editor_state(indoc! {"
 4823        «
 4824
 4825        aaaaa
 4826        bb
 4827        ccc
 4828        dddˇ»
 4829
 4830    "});
 4831
 4832    // Adding new line
 4833    cx.set_state(indoc! {"
 4834        aa«a
 4835        bbˇ»b
 4836    "});
 4837    cx.update_editor(|e, window, cx| {
 4838        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4839    });
 4840    cx.assert_editor_state(indoc! {"
 4841        «aaa
 4842        bbb
 4843        added_lineˇ»
 4844    "});
 4845
 4846    // Removing line
 4847    cx.set_state(indoc! {"
 4848        aa«a
 4849        bbbˇ»
 4850    "});
 4851    cx.update_editor(|e, window, cx| {
 4852        e.manipulate_immutable_lines(window, cx, |lines| {
 4853            lines.pop();
 4854        })
 4855    });
 4856    cx.assert_editor_state(indoc! {"
 4857        «aaaˇ»
 4858    "});
 4859
 4860    // Removing all lines
 4861    cx.set_state(indoc! {"
 4862        aa«a
 4863        bbbˇ»
 4864    "});
 4865    cx.update_editor(|e, window, cx| {
 4866        e.manipulate_immutable_lines(window, cx, |lines| {
 4867            lines.drain(..);
 4868        })
 4869    });
 4870    cx.assert_editor_state(indoc! {"
 4871        ˇ
 4872    "});
 4873}
 4874
 4875#[gpui::test]
 4876async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4877    init_test(cx, |_| {});
 4878
 4879    let mut cx = EditorTestContext::new(cx).await;
 4880
 4881    // Consider continuous selection as single selection
 4882    cx.set_state(indoc! {"
 4883        Aaa«aa
 4884        cˇ»c«c
 4885        bb
 4886        aaaˇ»aa
 4887    "});
 4888    cx.update_editor(|e, window, cx| {
 4889        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4890    });
 4891    cx.assert_editor_state(indoc! {"
 4892        «Aaaaa
 4893        ccc
 4894        bb
 4895        aaaaaˇ»
 4896    "});
 4897
 4898    cx.set_state(indoc! {"
 4899        Aaa«aa
 4900        cˇ»c«c
 4901        bb
 4902        aaaˇ»aa
 4903    "});
 4904    cx.update_editor(|e, window, cx| {
 4905        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4906    });
 4907    cx.assert_editor_state(indoc! {"
 4908        «Aaaaa
 4909        ccc
 4910        bbˇ»
 4911    "});
 4912
 4913    // Consider non continuous selection as distinct dedup operations
 4914    cx.set_state(indoc! {"
 4915        «aaaaa
 4916        bb
 4917        aaaaa
 4918        aaaaaˇ»
 4919
 4920        aaa«aaˇ»
 4921    "});
 4922    cx.update_editor(|e, window, cx| {
 4923        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4924    });
 4925    cx.assert_editor_state(indoc! {"
 4926        «aaaaa
 4927        bbˇ»
 4928
 4929        «aaaaaˇ»
 4930    "});
 4931}
 4932
 4933#[gpui::test]
 4934async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4935    init_test(cx, |_| {});
 4936
 4937    let mut cx = EditorTestContext::new(cx).await;
 4938
 4939    cx.set_state(indoc! {"
 4940        «Aaa
 4941        aAa
 4942        Aaaˇ»
 4943    "});
 4944    cx.update_editor(|e, window, cx| {
 4945        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4946    });
 4947    cx.assert_editor_state(indoc! {"
 4948        «Aaa
 4949        aAaˇ»
 4950    "});
 4951
 4952    cx.set_state(indoc! {"
 4953        «Aaa
 4954        aAa
 4955        aaAˇ»
 4956    "});
 4957    cx.update_editor(|e, window, cx| {
 4958        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4959    });
 4960    cx.assert_editor_state(indoc! {"
 4961        «Aaaˇ»
 4962    "});
 4963}
 4964
 4965#[gpui::test]
 4966async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4967    init_test(cx, |_| {});
 4968
 4969    let mut cx = EditorTestContext::new(cx).await;
 4970
 4971    let js_language = Arc::new(Language::new(
 4972        LanguageConfig {
 4973            name: "JavaScript".into(),
 4974            wrap_characters: Some(language::WrapCharactersConfig {
 4975                start_prefix: "<".into(),
 4976                start_suffix: ">".into(),
 4977                end_prefix: "</".into(),
 4978                end_suffix: ">".into(),
 4979            }),
 4980            ..LanguageConfig::default()
 4981        },
 4982        None,
 4983    ));
 4984
 4985    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4986
 4987    cx.set_state(indoc! {"
 4988        «testˇ»
 4989    "});
 4990    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4991    cx.assert_editor_state(indoc! {"
 4992        <«ˇ»>test</«ˇ»>
 4993    "});
 4994
 4995    cx.set_state(indoc! {"
 4996        «test
 4997         testˇ»
 4998    "});
 4999    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5000    cx.assert_editor_state(indoc! {"
 5001        <«ˇ»>test
 5002         test</«ˇ»>
 5003    "});
 5004
 5005    cx.set_state(indoc! {"
 5006        teˇst
 5007    "});
 5008    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5009    cx.assert_editor_state(indoc! {"
 5010        te<«ˇ»></«ˇ»>st
 5011    "});
 5012}
 5013
 5014#[gpui::test]
 5015async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5016    init_test(cx, |_| {});
 5017
 5018    let mut cx = EditorTestContext::new(cx).await;
 5019
 5020    let js_language = Arc::new(Language::new(
 5021        LanguageConfig {
 5022            name: "JavaScript".into(),
 5023            wrap_characters: Some(language::WrapCharactersConfig {
 5024                start_prefix: "<".into(),
 5025                start_suffix: ">".into(),
 5026                end_prefix: "</".into(),
 5027                end_suffix: ">".into(),
 5028            }),
 5029            ..LanguageConfig::default()
 5030        },
 5031        None,
 5032    ));
 5033
 5034    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5035
 5036    cx.set_state(indoc! {"
 5037        «testˇ»
 5038        «testˇ» «testˇ»
 5039        «testˇ»
 5040    "});
 5041    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5042    cx.assert_editor_state(indoc! {"
 5043        <«ˇ»>test</«ˇ»>
 5044        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5045        <«ˇ»>test</«ˇ»>
 5046    "});
 5047
 5048    cx.set_state(indoc! {"
 5049        «test
 5050         testˇ»
 5051        «test
 5052         testˇ»
 5053    "});
 5054    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5055    cx.assert_editor_state(indoc! {"
 5056        <«ˇ»>test
 5057         test</«ˇ»>
 5058        <«ˇ»>test
 5059         test</«ˇ»>
 5060    "});
 5061}
 5062
 5063#[gpui::test]
 5064async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5065    init_test(cx, |_| {});
 5066
 5067    let mut cx = EditorTestContext::new(cx).await;
 5068
 5069    let plaintext_language = Arc::new(Language::new(
 5070        LanguageConfig {
 5071            name: "Plain Text".into(),
 5072            ..LanguageConfig::default()
 5073        },
 5074        None,
 5075    ));
 5076
 5077    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5078
 5079    cx.set_state(indoc! {"
 5080        «testˇ»
 5081    "});
 5082    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5083    cx.assert_editor_state(indoc! {"
 5084      «testˇ»
 5085    "});
 5086}
 5087
 5088#[gpui::test]
 5089async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5090    init_test(cx, |_| {});
 5091
 5092    let mut cx = EditorTestContext::new(cx).await;
 5093
 5094    // Manipulate with multiple selections on a single line
 5095    cx.set_state(indoc! {"
 5096        dd«dd
 5097        cˇ»c«c
 5098        bb
 5099        aaaˇ»aa
 5100    "});
 5101    cx.update_editor(|e, window, cx| {
 5102        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5103    });
 5104    cx.assert_editor_state(indoc! {"
 5105        «aaaaa
 5106        bb
 5107        ccc
 5108        ddddˇ»
 5109    "});
 5110
 5111    // Manipulate with multiple disjoin selections
 5112    cx.set_state(indoc! {"
 5113 5114        4
 5115        3
 5116        2
 5117        1ˇ»
 5118
 5119        dd«dd
 5120        ccc
 5121        bb
 5122        aaaˇ»aa
 5123    "});
 5124    cx.update_editor(|e, window, cx| {
 5125        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5126    });
 5127    cx.assert_editor_state(indoc! {"
 5128        «1
 5129        2
 5130        3
 5131        4
 5132        5ˇ»
 5133
 5134        «aaaaa
 5135        bb
 5136        ccc
 5137        ddddˇ»
 5138    "});
 5139
 5140    // Adding lines on each selection
 5141    cx.set_state(indoc! {"
 5142 5143        1ˇ»
 5144
 5145        bb«bb
 5146        aaaˇ»aa
 5147    "});
 5148    cx.update_editor(|e, window, cx| {
 5149        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5150    });
 5151    cx.assert_editor_state(indoc! {"
 5152        «2
 5153        1
 5154        added lineˇ»
 5155
 5156        «bbbb
 5157        aaaaa
 5158        added lineˇ»
 5159    "});
 5160
 5161    // Removing lines on each selection
 5162    cx.set_state(indoc! {"
 5163 5164        1ˇ»
 5165
 5166        bb«bb
 5167        aaaˇ»aa
 5168    "});
 5169    cx.update_editor(|e, window, cx| {
 5170        e.manipulate_immutable_lines(window, cx, |lines| {
 5171            lines.pop();
 5172        })
 5173    });
 5174    cx.assert_editor_state(indoc! {"
 5175        «2ˇ»
 5176
 5177        «bbbbˇ»
 5178    "});
 5179}
 5180
 5181#[gpui::test]
 5182async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5183    init_test(cx, |settings| {
 5184        settings.defaults.tab_size = NonZeroU32::new(3)
 5185    });
 5186
 5187    let mut cx = EditorTestContext::new(cx).await;
 5188
 5189    // MULTI SELECTION
 5190    // Ln.1 "«" tests empty lines
 5191    // Ln.9 tests just leading whitespace
 5192    cx.set_state(indoc! {"
 5193        «
 5194        abc                 // No indentationˇ»
 5195        «\tabc              // 1 tabˇ»
 5196        \t\tabc «      ˇ»   // 2 tabs
 5197        \t ab«c             // Tab followed by space
 5198         \tabc              // Space followed by tab (3 spaces should be the result)
 5199        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5200           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5201        \t
 5202        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5203    "});
 5204    cx.update_editor(|e, window, cx| {
 5205        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5206    });
 5207    cx.assert_editor_state(
 5208        indoc! {"
 5209            «
 5210            abc                 // No indentation
 5211               abc              // 1 tab
 5212                  abc          // 2 tabs
 5213                abc             // Tab followed by space
 5214               abc              // Space followed by tab (3 spaces should be the result)
 5215                           abc   // Mixed indentation (tab conversion depends on the column)
 5216               abc         // Already space indented
 5217               ·
 5218               abc\tdef          // Only the leading tab is manipulatedˇ»
 5219        "}
 5220        .replace("·", "")
 5221        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5222    );
 5223
 5224    // Test on just a few lines, the others should remain unchanged
 5225    // Only lines (3, 5, 10, 11) should change
 5226    cx.set_state(
 5227        indoc! {"
 5228            ·
 5229            abc                 // No indentation
 5230            \tabcˇ               // 1 tab
 5231            \t\tabc             // 2 tabs
 5232            \t abcˇ              // Tab followed by space
 5233             \tabc              // Space followed by tab (3 spaces should be the result)
 5234            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5235               abc              // Already space indented
 5236            «\t
 5237            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5238        "}
 5239        .replace("·", "")
 5240        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5241    );
 5242    cx.update_editor(|e, window, cx| {
 5243        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5244    });
 5245    cx.assert_editor_state(
 5246        indoc! {"
 5247            ·
 5248            abc                 // No indentation
 5249            «   abc               // 1 tabˇ»
 5250            \t\tabc             // 2 tabs
 5251            «    abc              // Tab followed by spaceˇ»
 5252             \tabc              // Space followed by tab (3 spaces should be the result)
 5253            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5254               abc              // Already space indented
 5255            «   ·
 5256               abc\tdef          // Only the leading tab is manipulatedˇ»
 5257        "}
 5258        .replace("·", "")
 5259        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5260    );
 5261
 5262    // SINGLE SELECTION
 5263    // Ln.1 "«" tests empty lines
 5264    // Ln.9 tests just leading whitespace
 5265    cx.set_state(indoc! {"
 5266        «
 5267        abc                 // No indentation
 5268        \tabc               // 1 tab
 5269        \t\tabc             // 2 tabs
 5270        \t abc              // Tab followed by space
 5271         \tabc              // Space followed by tab (3 spaces should be the result)
 5272        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5273           abc              // Already space indented
 5274        \t
 5275        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5276    "});
 5277    cx.update_editor(|e, window, cx| {
 5278        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5279    });
 5280    cx.assert_editor_state(
 5281        indoc! {"
 5282            «
 5283            abc                 // No indentation
 5284               abc               // 1 tab
 5285                  abc             // 2 tabs
 5286                abc              // Tab followed by space
 5287               abc              // Space followed by tab (3 spaces should be the result)
 5288                           abc   // Mixed indentation (tab conversion depends on the column)
 5289               abc              // Already space indented
 5290               ·
 5291               abc\tdef          // Only the leading tab is manipulatedˇ»
 5292        "}
 5293        .replace("·", "")
 5294        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5295    );
 5296}
 5297
 5298#[gpui::test]
 5299async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5300    init_test(cx, |settings| {
 5301        settings.defaults.tab_size = NonZeroU32::new(3)
 5302    });
 5303
 5304    let mut cx = EditorTestContext::new(cx).await;
 5305
 5306    // MULTI SELECTION
 5307    // Ln.1 "«" tests empty lines
 5308    // Ln.11 tests just leading whitespace
 5309    cx.set_state(indoc! {"
 5310        «
 5311        abˇ»ˇc                 // No indentation
 5312         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5313          abc  «             // 2 spaces (< 3 so dont convert)
 5314           abc              // 3 spaces (convert)
 5315             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5316        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5317        «\t abc              // Tab followed by space
 5318         \tabc              // Space followed by tab (should be consumed due to tab)
 5319        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5320           \tˇ»  «\t
 5321           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5322    "});
 5323    cx.update_editor(|e, window, cx| {
 5324        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5325    });
 5326    cx.assert_editor_state(indoc! {"
 5327        «
 5328        abc                 // No indentation
 5329         abc                // 1 space (< 3 so dont convert)
 5330          abc               // 2 spaces (< 3 so dont convert)
 5331        \tabc              // 3 spaces (convert)
 5332        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5333        \t\t\tabc           // Already tab indented
 5334        \t abc              // Tab followed by space
 5335        \tabc              // Space followed by tab (should be consumed due to tab)
 5336        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5337        \t\t\t
 5338        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5339    "});
 5340
 5341    // Test on just a few lines, the other should remain unchanged
 5342    // Only lines (4, 8, 11, 12) should change
 5343    cx.set_state(
 5344        indoc! {"
 5345            ·
 5346            abc                 // No indentation
 5347             abc                // 1 space (< 3 so dont convert)
 5348              abc               // 2 spaces (< 3 so dont convert)
 5349            «   abc              // 3 spaces (convert)ˇ»
 5350                 abc            // 5 spaces (1 tab + 2 spaces)
 5351            \t\t\tabc           // Already tab indented
 5352            \t abc              // Tab followed by space
 5353             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5354               \t\t  \tabc      // Mixed indentation
 5355            \t \t  \t   \tabc   // Mixed indentation
 5356               \t  \tˇ
 5357            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5358        "}
 5359        .replace("·", "")
 5360        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5361    );
 5362    cx.update_editor(|e, window, cx| {
 5363        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5364    });
 5365    cx.assert_editor_state(
 5366        indoc! {"
 5367            ·
 5368            abc                 // No indentation
 5369             abc                // 1 space (< 3 so dont convert)
 5370              abc               // 2 spaces (< 3 so dont convert)
 5371            «\tabc              // 3 spaces (convert)ˇ»
 5372                 abc            // 5 spaces (1 tab + 2 spaces)
 5373            \t\t\tabc           // Already tab indented
 5374            \t abc              // Tab followed by space
 5375            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5376               \t\t  \tabc      // Mixed indentation
 5377            \t \t  \t   \tabc   // Mixed indentation
 5378            «\t\t\t
 5379            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5380        "}
 5381        .replace("·", "")
 5382        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5383    );
 5384
 5385    // SINGLE SELECTION
 5386    // Ln.1 "«" tests empty lines
 5387    // Ln.11 tests just leading whitespace
 5388    cx.set_state(indoc! {"
 5389        «
 5390        abc                 // No indentation
 5391         abc                // 1 space (< 3 so dont convert)
 5392          abc               // 2 spaces (< 3 so dont convert)
 5393           abc              // 3 spaces (convert)
 5394             abc            // 5 spaces (1 tab + 2 spaces)
 5395        \t\t\tabc           // Already tab indented
 5396        \t abc              // Tab followed by space
 5397         \tabc              // Space followed by tab (should be consumed due to tab)
 5398        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5399           \t  \t
 5400           abc   \t         // Only the leading spaces should be convertedˇ»
 5401    "});
 5402    cx.update_editor(|e, window, cx| {
 5403        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5404    });
 5405    cx.assert_editor_state(indoc! {"
 5406        «
 5407        abc                 // No indentation
 5408         abc                // 1 space (< 3 so dont convert)
 5409          abc               // 2 spaces (< 3 so dont convert)
 5410        \tabc              // 3 spaces (convert)
 5411        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5412        \t\t\tabc           // Already tab indented
 5413        \t abc              // Tab followed by space
 5414        \tabc              // Space followed by tab (should be consumed due to tab)
 5415        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5416        \t\t\t
 5417        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5418    "});
 5419}
 5420
 5421#[gpui::test]
 5422async fn test_toggle_case(cx: &mut TestAppContext) {
 5423    init_test(cx, |_| {});
 5424
 5425    let mut cx = EditorTestContext::new(cx).await;
 5426
 5427    // If all lower case -> upper case
 5428    cx.set_state(indoc! {"
 5429        «hello worldˇ»
 5430    "});
 5431    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5432    cx.assert_editor_state(indoc! {"
 5433        «HELLO WORLDˇ»
 5434    "});
 5435
 5436    // If all upper case -> lower case
 5437    cx.set_state(indoc! {"
 5438        «HELLO WORLDˇ»
 5439    "});
 5440    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5441    cx.assert_editor_state(indoc! {"
 5442        «hello worldˇ»
 5443    "});
 5444
 5445    // If any upper case characters are identified -> lower case
 5446    // This matches JetBrains IDEs
 5447    cx.set_state(indoc! {"
 5448        «hEllo worldˇ»
 5449    "});
 5450    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5451    cx.assert_editor_state(indoc! {"
 5452        «hello worldˇ»
 5453    "});
 5454}
 5455
 5456#[gpui::test]
 5457async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5458    init_test(cx, |_| {});
 5459
 5460    let mut cx = EditorTestContext::new(cx).await;
 5461
 5462    cx.set_state(indoc! {"
 5463        «implement-windows-supportˇ»
 5464    "});
 5465    cx.update_editor(|e, window, cx| {
 5466        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5467    });
 5468    cx.assert_editor_state(indoc! {"
 5469        «Implement windows supportˇ»
 5470    "});
 5471}
 5472
 5473#[gpui::test]
 5474async fn test_manipulate_text(cx: &mut TestAppContext) {
 5475    init_test(cx, |_| {});
 5476
 5477    let mut cx = EditorTestContext::new(cx).await;
 5478
 5479    // Test convert_to_upper_case()
 5480    cx.set_state(indoc! {"
 5481        «hello worldˇ»
 5482    "});
 5483    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5484    cx.assert_editor_state(indoc! {"
 5485        «HELLO WORLDˇ»
 5486    "});
 5487
 5488    // Test convert_to_lower_case()
 5489    cx.set_state(indoc! {"
 5490        «HELLO WORLDˇ»
 5491    "});
 5492    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5493    cx.assert_editor_state(indoc! {"
 5494        «hello worldˇ»
 5495    "});
 5496
 5497    // Test multiple line, single selection case
 5498    cx.set_state(indoc! {"
 5499        «The quick brown
 5500        fox jumps over
 5501        the lazy dogˇ»
 5502    "});
 5503    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5504    cx.assert_editor_state(indoc! {"
 5505        «The Quick Brown
 5506        Fox Jumps Over
 5507        The Lazy Dogˇ»
 5508    "});
 5509
 5510    // Test multiple line, single selection case
 5511    cx.set_state(indoc! {"
 5512        «The quick brown
 5513        fox jumps over
 5514        the lazy dogˇ»
 5515    "});
 5516    cx.update_editor(|e, window, cx| {
 5517        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5518    });
 5519    cx.assert_editor_state(indoc! {"
 5520        «TheQuickBrown
 5521        FoxJumpsOver
 5522        TheLazyDogˇ»
 5523    "});
 5524
 5525    // From here on out, test more complex cases of manipulate_text()
 5526
 5527    // Test no selection case - should affect words cursors are in
 5528    // Cursor at beginning, middle, and end of word
 5529    cx.set_state(indoc! {"
 5530        ˇhello big beauˇtiful worldˇ
 5531    "});
 5532    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5533    cx.assert_editor_state(indoc! {"
 5534        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5535    "});
 5536
 5537    // Test multiple selections on a single line and across multiple lines
 5538    cx.set_state(indoc! {"
 5539        «Theˇ» quick «brown
 5540        foxˇ» jumps «overˇ»
 5541        the «lazyˇ» dog
 5542    "});
 5543    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5544    cx.assert_editor_state(indoc! {"
 5545        «THEˇ» quick «BROWN
 5546        FOXˇ» jumps «OVERˇ»
 5547        the «LAZYˇ» dog
 5548    "});
 5549
 5550    // Test case where text length grows
 5551    cx.set_state(indoc! {"
 5552        «tschüߡ»
 5553    "});
 5554    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5555    cx.assert_editor_state(indoc! {"
 5556        «TSCHÜSSˇ»
 5557    "});
 5558
 5559    // Test to make sure we don't crash when text shrinks
 5560    cx.set_state(indoc! {"
 5561        aaa_bbbˇ
 5562    "});
 5563    cx.update_editor(|e, window, cx| {
 5564        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5565    });
 5566    cx.assert_editor_state(indoc! {"
 5567        «aaaBbbˇ»
 5568    "});
 5569
 5570    // Test to make sure we all aware of the fact that each word can grow and shrink
 5571    // Final selections should be aware of this fact
 5572    cx.set_state(indoc! {"
 5573        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5574    "});
 5575    cx.update_editor(|e, window, cx| {
 5576        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5577    });
 5578    cx.assert_editor_state(indoc! {"
 5579        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5580    "});
 5581
 5582    cx.set_state(indoc! {"
 5583        «hElLo, WoRld!ˇ»
 5584    "});
 5585    cx.update_editor(|e, window, cx| {
 5586        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5587    });
 5588    cx.assert_editor_state(indoc! {"
 5589        «HeLlO, wOrLD!ˇ»
 5590    "});
 5591
 5592    // Test selections with `line_mode() = true`.
 5593    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5594    cx.set_state(indoc! {"
 5595        «The quick brown
 5596        fox jumps over
 5597        tˇ»he lazy dog
 5598    "});
 5599    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5600    cx.assert_editor_state(indoc! {"
 5601        «THE QUICK BROWN
 5602        FOX JUMPS OVER
 5603        THE LAZY DOGˇ»
 5604    "});
 5605}
 5606
 5607#[gpui::test]
 5608fn test_duplicate_line(cx: &mut TestAppContext) {
 5609    init_test(cx, |_| {});
 5610
 5611    let editor = cx.add_window(|window, cx| {
 5612        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5613        build_editor(buffer, window, cx)
 5614    });
 5615    _ = editor.update(cx, |editor, window, cx| {
 5616        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5617            s.select_display_ranges([
 5618                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5619                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5620                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5621                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5622            ])
 5623        });
 5624        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5625        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5626        assert_eq!(
 5627            display_ranges(editor, cx),
 5628            vec![
 5629                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5630                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5631                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5632                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5633            ]
 5634        );
 5635    });
 5636
 5637    let editor = cx.add_window(|window, cx| {
 5638        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5639        build_editor(buffer, window, cx)
 5640    });
 5641    _ = editor.update(cx, |editor, window, cx| {
 5642        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5643            s.select_display_ranges([
 5644                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5645                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5646            ])
 5647        });
 5648        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5649        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5650        assert_eq!(
 5651            display_ranges(editor, cx),
 5652            vec![
 5653                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5654                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5655            ]
 5656        );
 5657    });
 5658
 5659    // With `duplicate_line_up` the selections move to the duplicated lines,
 5660    // which are inserted above the original lines
 5661    let editor = cx.add_window(|window, cx| {
 5662        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5663        build_editor(buffer, window, cx)
 5664    });
 5665    _ = editor.update(cx, |editor, window, cx| {
 5666        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5667            s.select_display_ranges([
 5668                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5669                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5670                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5671                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5672            ])
 5673        });
 5674        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5675        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5676        assert_eq!(
 5677            display_ranges(editor, cx),
 5678            vec![
 5679                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5680                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5681                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5682                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5683            ]
 5684        );
 5685    });
 5686
 5687    let editor = cx.add_window(|window, cx| {
 5688        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5689        build_editor(buffer, window, cx)
 5690    });
 5691    _ = editor.update(cx, |editor, window, cx| {
 5692        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5693            s.select_display_ranges([
 5694                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5695                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5696            ])
 5697        });
 5698        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5699        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5700        assert_eq!(
 5701            display_ranges(editor, cx),
 5702            vec![
 5703                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5704                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5705            ]
 5706        );
 5707    });
 5708
 5709    let editor = cx.add_window(|window, cx| {
 5710        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5711        build_editor(buffer, window, cx)
 5712    });
 5713    _ = editor.update(cx, |editor, window, cx| {
 5714        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5715            s.select_display_ranges([
 5716                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5717                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5718            ])
 5719        });
 5720        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5721        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5722        assert_eq!(
 5723            display_ranges(editor, cx),
 5724            vec![
 5725                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5726                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5727            ]
 5728        );
 5729    });
 5730}
 5731
 5732#[gpui::test]
 5733fn test_move_line_up_down(cx: &mut TestAppContext) {
 5734    init_test(cx, |_| {});
 5735
 5736    let editor = cx.add_window(|window, cx| {
 5737        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5738        build_editor(buffer, window, cx)
 5739    });
 5740    _ = editor.update(cx, |editor, window, cx| {
 5741        editor.fold_creases(
 5742            vec![
 5743                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5744                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5745                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5746            ],
 5747            true,
 5748            window,
 5749            cx,
 5750        );
 5751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5752            s.select_display_ranges([
 5753                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5754                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5755                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5756                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5757            ])
 5758        });
 5759        assert_eq!(
 5760            editor.display_text(cx),
 5761            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5762        );
 5763
 5764        editor.move_line_up(&MoveLineUp, window, cx);
 5765        assert_eq!(
 5766            editor.display_text(cx),
 5767            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5768        );
 5769        assert_eq!(
 5770            display_ranges(editor, cx),
 5771            vec![
 5772                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5773                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5774                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5775                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5776            ]
 5777        );
 5778    });
 5779
 5780    _ = editor.update(cx, |editor, window, cx| {
 5781        editor.move_line_down(&MoveLineDown, window, cx);
 5782        assert_eq!(
 5783            editor.display_text(cx),
 5784            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5785        );
 5786        assert_eq!(
 5787            display_ranges(editor, cx),
 5788            vec![
 5789                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5790                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5791                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5792                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5793            ]
 5794        );
 5795    });
 5796
 5797    _ = editor.update(cx, |editor, window, cx| {
 5798        editor.move_line_down(&MoveLineDown, window, cx);
 5799        assert_eq!(
 5800            editor.display_text(cx),
 5801            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5802        );
 5803        assert_eq!(
 5804            display_ranges(editor, cx),
 5805            vec![
 5806                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5807                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5808                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5809                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5810            ]
 5811        );
 5812    });
 5813
 5814    _ = editor.update(cx, |editor, window, cx| {
 5815        editor.move_line_up(&MoveLineUp, window, cx);
 5816        assert_eq!(
 5817            editor.display_text(cx),
 5818            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5819        );
 5820        assert_eq!(
 5821            display_ranges(editor, cx),
 5822            vec![
 5823                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5824                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5825                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5826                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5827            ]
 5828        );
 5829    });
 5830}
 5831
 5832#[gpui::test]
 5833fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5834    init_test(cx, |_| {});
 5835    let editor = cx.add_window(|window, cx| {
 5836        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5837        build_editor(buffer, window, cx)
 5838    });
 5839    _ = editor.update(cx, |editor, window, cx| {
 5840        editor.fold_creases(
 5841            vec![Crease::simple(
 5842                Point::new(6, 4)..Point::new(7, 4),
 5843                FoldPlaceholder::test(),
 5844            )],
 5845            true,
 5846            window,
 5847            cx,
 5848        );
 5849        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5850            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5851        });
 5852        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5853        editor.move_line_up(&MoveLineUp, window, cx);
 5854        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5855        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5856    });
 5857}
 5858
 5859#[gpui::test]
 5860fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5861    init_test(cx, |_| {});
 5862
 5863    let editor = cx.add_window(|window, cx| {
 5864        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5865        build_editor(buffer, window, cx)
 5866    });
 5867    _ = editor.update(cx, |editor, window, cx| {
 5868        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5869        editor.insert_blocks(
 5870            [BlockProperties {
 5871                style: BlockStyle::Fixed,
 5872                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5873                height: Some(1),
 5874                render: Arc::new(|_| div().into_any()),
 5875                priority: 0,
 5876            }],
 5877            Some(Autoscroll::fit()),
 5878            cx,
 5879        );
 5880        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5881            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5882        });
 5883        editor.move_line_down(&MoveLineDown, window, cx);
 5884    });
 5885}
 5886
 5887#[gpui::test]
 5888async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5889    init_test(cx, |_| {});
 5890
 5891    let mut cx = EditorTestContext::new(cx).await;
 5892    cx.set_state(
 5893        &"
 5894            ˇzero
 5895            one
 5896            two
 5897            three
 5898            four
 5899            five
 5900        "
 5901        .unindent(),
 5902    );
 5903
 5904    // Create a four-line block that replaces three lines of text.
 5905    cx.update_editor(|editor, window, cx| {
 5906        let snapshot = editor.snapshot(window, cx);
 5907        let snapshot = &snapshot.buffer_snapshot();
 5908        let placement = BlockPlacement::Replace(
 5909            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5910        );
 5911        editor.insert_blocks(
 5912            [BlockProperties {
 5913                placement,
 5914                height: Some(4),
 5915                style: BlockStyle::Sticky,
 5916                render: Arc::new(|_| gpui::div().into_any_element()),
 5917                priority: 0,
 5918            }],
 5919            None,
 5920            cx,
 5921        );
 5922    });
 5923
 5924    // Move down so that the cursor touches the block.
 5925    cx.update_editor(|editor, window, cx| {
 5926        editor.move_down(&Default::default(), window, cx);
 5927    });
 5928    cx.assert_editor_state(
 5929        &"
 5930            zero
 5931            «one
 5932            two
 5933            threeˇ»
 5934            four
 5935            five
 5936        "
 5937        .unindent(),
 5938    );
 5939
 5940    // Move down past the block.
 5941    cx.update_editor(|editor, window, cx| {
 5942        editor.move_down(&Default::default(), window, cx);
 5943    });
 5944    cx.assert_editor_state(
 5945        &"
 5946            zero
 5947            one
 5948            two
 5949            three
 5950            ˇfour
 5951            five
 5952        "
 5953        .unindent(),
 5954    );
 5955}
 5956
 5957#[gpui::test]
 5958fn test_transpose(cx: &mut TestAppContext) {
 5959    init_test(cx, |_| {});
 5960
 5961    _ = cx.add_window(|window, cx| {
 5962        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5963        editor.set_style(EditorStyle::default(), window, cx);
 5964        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5965            s.select_ranges([1..1])
 5966        });
 5967        editor.transpose(&Default::default(), window, cx);
 5968        assert_eq!(editor.text(cx), "bac");
 5969        assert_eq!(
 5970            editor.selections.ranges(&editor.display_snapshot(cx)),
 5971            [2..2]
 5972        );
 5973
 5974        editor.transpose(&Default::default(), window, cx);
 5975        assert_eq!(editor.text(cx), "bca");
 5976        assert_eq!(
 5977            editor.selections.ranges(&editor.display_snapshot(cx)),
 5978            [3..3]
 5979        );
 5980
 5981        editor.transpose(&Default::default(), window, cx);
 5982        assert_eq!(editor.text(cx), "bac");
 5983        assert_eq!(
 5984            editor.selections.ranges(&editor.display_snapshot(cx)),
 5985            [3..3]
 5986        );
 5987
 5988        editor
 5989    });
 5990
 5991    _ = cx.add_window(|window, cx| {
 5992        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5993        editor.set_style(EditorStyle::default(), window, cx);
 5994        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5995            s.select_ranges([3..3])
 5996        });
 5997        editor.transpose(&Default::default(), window, cx);
 5998        assert_eq!(editor.text(cx), "acb\nde");
 5999        assert_eq!(
 6000            editor.selections.ranges(&editor.display_snapshot(cx)),
 6001            [3..3]
 6002        );
 6003
 6004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6005            s.select_ranges([4..4])
 6006        });
 6007        editor.transpose(&Default::default(), window, cx);
 6008        assert_eq!(editor.text(cx), "acbd\ne");
 6009        assert_eq!(
 6010            editor.selections.ranges(&editor.display_snapshot(cx)),
 6011            [5..5]
 6012        );
 6013
 6014        editor.transpose(&Default::default(), window, cx);
 6015        assert_eq!(editor.text(cx), "acbde\n");
 6016        assert_eq!(
 6017            editor.selections.ranges(&editor.display_snapshot(cx)),
 6018            [6..6]
 6019        );
 6020
 6021        editor.transpose(&Default::default(), window, cx);
 6022        assert_eq!(editor.text(cx), "acbd\ne");
 6023        assert_eq!(
 6024            editor.selections.ranges(&editor.display_snapshot(cx)),
 6025            [6..6]
 6026        );
 6027
 6028        editor
 6029    });
 6030
 6031    _ = cx.add_window(|window, cx| {
 6032        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6033        editor.set_style(EditorStyle::default(), window, cx);
 6034        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6035            s.select_ranges([1..1, 2..2, 4..4])
 6036        });
 6037        editor.transpose(&Default::default(), window, cx);
 6038        assert_eq!(editor.text(cx), "bacd\ne");
 6039        assert_eq!(
 6040            editor.selections.ranges(&editor.display_snapshot(cx)),
 6041            [2..2, 3..3, 5..5]
 6042        );
 6043
 6044        editor.transpose(&Default::default(), window, cx);
 6045        assert_eq!(editor.text(cx), "bcade\n");
 6046        assert_eq!(
 6047            editor.selections.ranges(&editor.display_snapshot(cx)),
 6048            [3..3, 4..4, 6..6]
 6049        );
 6050
 6051        editor.transpose(&Default::default(), window, cx);
 6052        assert_eq!(editor.text(cx), "bcda\ne");
 6053        assert_eq!(
 6054            editor.selections.ranges(&editor.display_snapshot(cx)),
 6055            [4..4, 6..6]
 6056        );
 6057
 6058        editor.transpose(&Default::default(), window, cx);
 6059        assert_eq!(editor.text(cx), "bcade\n");
 6060        assert_eq!(
 6061            editor.selections.ranges(&editor.display_snapshot(cx)),
 6062            [4..4, 6..6]
 6063        );
 6064
 6065        editor.transpose(&Default::default(), window, cx);
 6066        assert_eq!(editor.text(cx), "bcaed\n");
 6067        assert_eq!(
 6068            editor.selections.ranges(&editor.display_snapshot(cx)),
 6069            [5..5, 6..6]
 6070        );
 6071
 6072        editor
 6073    });
 6074
 6075    _ = cx.add_window(|window, cx| {
 6076        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6077        editor.set_style(EditorStyle::default(), window, cx);
 6078        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6079            s.select_ranges([4..4])
 6080        });
 6081        editor.transpose(&Default::default(), window, cx);
 6082        assert_eq!(editor.text(cx), "🏀🍐✋");
 6083        assert_eq!(
 6084            editor.selections.ranges(&editor.display_snapshot(cx)),
 6085            [8..8]
 6086        );
 6087
 6088        editor.transpose(&Default::default(), window, cx);
 6089        assert_eq!(editor.text(cx), "🏀✋🍐");
 6090        assert_eq!(
 6091            editor.selections.ranges(&editor.display_snapshot(cx)),
 6092            [11..11]
 6093        );
 6094
 6095        editor.transpose(&Default::default(), window, cx);
 6096        assert_eq!(editor.text(cx), "🏀🍐✋");
 6097        assert_eq!(
 6098            editor.selections.ranges(&editor.display_snapshot(cx)),
 6099            [11..11]
 6100        );
 6101
 6102        editor
 6103    });
 6104}
 6105
 6106#[gpui::test]
 6107async fn test_rewrap(cx: &mut TestAppContext) {
 6108    init_test(cx, |settings| {
 6109        settings.languages.0.extend([
 6110            (
 6111                "Markdown".into(),
 6112                LanguageSettingsContent {
 6113                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6114                    preferred_line_length: Some(40),
 6115                    ..Default::default()
 6116                },
 6117            ),
 6118            (
 6119                "Plain Text".into(),
 6120                LanguageSettingsContent {
 6121                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6122                    preferred_line_length: Some(40),
 6123                    ..Default::default()
 6124                },
 6125            ),
 6126            (
 6127                "C++".into(),
 6128                LanguageSettingsContent {
 6129                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6130                    preferred_line_length: Some(40),
 6131                    ..Default::default()
 6132                },
 6133            ),
 6134            (
 6135                "Python".into(),
 6136                LanguageSettingsContent {
 6137                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6138                    preferred_line_length: Some(40),
 6139                    ..Default::default()
 6140                },
 6141            ),
 6142            (
 6143                "Rust".into(),
 6144                LanguageSettingsContent {
 6145                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6146                    preferred_line_length: Some(40),
 6147                    ..Default::default()
 6148                },
 6149            ),
 6150        ])
 6151    });
 6152
 6153    let mut cx = EditorTestContext::new(cx).await;
 6154
 6155    let cpp_language = Arc::new(Language::new(
 6156        LanguageConfig {
 6157            name: "C++".into(),
 6158            line_comments: vec!["// ".into()],
 6159            ..LanguageConfig::default()
 6160        },
 6161        None,
 6162    ));
 6163    let python_language = Arc::new(Language::new(
 6164        LanguageConfig {
 6165            name: "Python".into(),
 6166            line_comments: vec!["# ".into()],
 6167            ..LanguageConfig::default()
 6168        },
 6169        None,
 6170    ));
 6171    let markdown_language = Arc::new(Language::new(
 6172        LanguageConfig {
 6173            name: "Markdown".into(),
 6174            rewrap_prefixes: vec![
 6175                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6176                regex::Regex::new("[-*+]\\s+").unwrap(),
 6177            ],
 6178            ..LanguageConfig::default()
 6179        },
 6180        None,
 6181    ));
 6182    let rust_language = Arc::new(
 6183        Language::new(
 6184            LanguageConfig {
 6185                name: "Rust".into(),
 6186                line_comments: vec!["// ".into(), "/// ".into()],
 6187                ..LanguageConfig::default()
 6188            },
 6189            Some(tree_sitter_rust::LANGUAGE.into()),
 6190        )
 6191        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6192        .unwrap(),
 6193    );
 6194
 6195    let plaintext_language = Arc::new(Language::new(
 6196        LanguageConfig {
 6197            name: "Plain Text".into(),
 6198            ..LanguageConfig::default()
 6199        },
 6200        None,
 6201    ));
 6202
 6203    // Test basic rewrapping of a long line with a cursor
 6204    assert_rewrap(
 6205        indoc! {"
 6206            // ˇThis is a long comment that needs to be wrapped.
 6207        "},
 6208        indoc! {"
 6209            // ˇThis is a long comment that needs to
 6210            // be wrapped.
 6211        "},
 6212        cpp_language.clone(),
 6213        &mut cx,
 6214    );
 6215
 6216    // Test rewrapping a full selection
 6217    assert_rewrap(
 6218        indoc! {"
 6219            «// This selected long comment needs to be wrapped.ˇ»"
 6220        },
 6221        indoc! {"
 6222            «// This selected long comment needs to
 6223            // be wrapped.ˇ»"
 6224        },
 6225        cpp_language.clone(),
 6226        &mut cx,
 6227    );
 6228
 6229    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6230    assert_rewrap(
 6231        indoc! {"
 6232            // ˇThis is the first line.
 6233            // Thisˇ is the second line.
 6234            // This is the thirdˇ line, all part of one paragraph.
 6235         "},
 6236        indoc! {"
 6237            // ˇThis is the first line. Thisˇ is the
 6238            // second line. This is the thirdˇ line,
 6239            // all part of one paragraph.
 6240         "},
 6241        cpp_language.clone(),
 6242        &mut cx,
 6243    );
 6244
 6245    // Test multiple cursors in different paragraphs trigger separate rewraps
 6246    assert_rewrap(
 6247        indoc! {"
 6248            // ˇThis is the first paragraph, first line.
 6249            // ˇThis is the first paragraph, second line.
 6250
 6251            // ˇThis is the second paragraph, first line.
 6252            // ˇThis is the second paragraph, second line.
 6253        "},
 6254        indoc! {"
 6255            // ˇThis is the first paragraph, first
 6256            // line. ˇThis is the first paragraph,
 6257            // second line.
 6258
 6259            // ˇThis is the second paragraph, first
 6260            // line. ˇThis is the second paragraph,
 6261            // second line.
 6262        "},
 6263        cpp_language.clone(),
 6264        &mut cx,
 6265    );
 6266
 6267    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6268    assert_rewrap(
 6269        indoc! {"
 6270            «// A regular long long comment to be wrapped.
 6271            /// A documentation long comment to be wrapped.ˇ»
 6272          "},
 6273        indoc! {"
 6274            «// A regular long long comment to be
 6275            // wrapped.
 6276            /// A documentation long comment to be
 6277            /// wrapped.ˇ»
 6278          "},
 6279        rust_language.clone(),
 6280        &mut cx,
 6281    );
 6282
 6283    // Test that change in indentation level trigger seperate rewraps
 6284    assert_rewrap(
 6285        indoc! {"
 6286            fn foo() {
 6287                «// This is a long comment at the base indent.
 6288                    // This is a long comment at the next indent.ˇ»
 6289            }
 6290        "},
 6291        indoc! {"
 6292            fn foo() {
 6293                «// This is a long comment at the
 6294                // base indent.
 6295                    // This is a long comment at the
 6296                    // next indent.ˇ»
 6297            }
 6298        "},
 6299        rust_language.clone(),
 6300        &mut cx,
 6301    );
 6302
 6303    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6304    assert_rewrap(
 6305        indoc! {"
 6306            # ˇThis is a long comment using a pound sign.
 6307        "},
 6308        indoc! {"
 6309            # ˇThis is a long comment using a pound
 6310            # sign.
 6311        "},
 6312        python_language,
 6313        &mut cx,
 6314    );
 6315
 6316    // Test rewrapping only affects comments, not code even when selected
 6317    assert_rewrap(
 6318        indoc! {"
 6319            «/// This doc comment is long and should be wrapped.
 6320            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6321        "},
 6322        indoc! {"
 6323            «/// This doc comment is long and should
 6324            /// be wrapped.
 6325            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6326        "},
 6327        rust_language.clone(),
 6328        &mut cx,
 6329    );
 6330
 6331    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6332    assert_rewrap(
 6333        indoc! {"
 6334            # Header
 6335
 6336            A long long long line of markdown text to wrap.ˇ
 6337         "},
 6338        indoc! {"
 6339            # Header
 6340
 6341            A long long long line of markdown text
 6342            to wrap.ˇ
 6343         "},
 6344        markdown_language.clone(),
 6345        &mut cx,
 6346    );
 6347
 6348    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6349    assert_rewrap(
 6350        indoc! {"
 6351            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6352            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6353            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6354        "},
 6355        indoc! {"
 6356            «1. This is a numbered list item that is
 6357               very long and needs to be wrapped
 6358               properly.
 6359            2. This is a numbered list item that is
 6360               very long and needs to be wrapped
 6361               properly.
 6362            - This is an unordered list item that is
 6363              also very long and should not merge
 6364              with the numbered item.ˇ»
 6365        "},
 6366        markdown_language.clone(),
 6367        &mut cx,
 6368    );
 6369
 6370    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6371    assert_rewrap(
 6372        indoc! {"
 6373            «1. This is a numbered list item that is
 6374            very long and needs to be wrapped
 6375            properly.
 6376            2. This is a numbered list item that is
 6377            very long and needs to be wrapped
 6378            properly.
 6379            - This is an unordered list item that is
 6380            also very long and should not merge with
 6381            the numbered item.ˇ»
 6382        "},
 6383        indoc! {"
 6384            «1. This is a numbered list item that is
 6385               very long and needs to be wrapped
 6386               properly.
 6387            2. This is a numbered list item that is
 6388               very long and needs to be wrapped
 6389               properly.
 6390            - This is an unordered list item that is
 6391              also very long and should not merge
 6392              with the numbered item.ˇ»
 6393        "},
 6394        markdown_language.clone(),
 6395        &mut cx,
 6396    );
 6397
 6398    // Test that rewrapping maintain indents even when they already exists.
 6399    assert_rewrap(
 6400        indoc! {"
 6401            «1. This is a numbered list
 6402               item that is very long and needs to be wrapped properly.
 6403            2. This is a numbered list
 6404               item that is very long and needs to be wrapped properly.
 6405            - This is an unordered list item that is also very long and
 6406              should not merge with the numbered item.ˇ»
 6407        "},
 6408        indoc! {"
 6409            «1. This is a numbered list item that is
 6410               very long and needs to be wrapped
 6411               properly.
 6412            2. This is a numbered list item that is
 6413               very long and needs to be wrapped
 6414               properly.
 6415            - This is an unordered list item that is
 6416              also very long and should not merge
 6417              with the numbered item.ˇ»
 6418        "},
 6419        markdown_language,
 6420        &mut cx,
 6421    );
 6422
 6423    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6424    assert_rewrap(
 6425        indoc! {"
 6426            ˇThis is a very long line of plain text that will be wrapped.
 6427        "},
 6428        indoc! {"
 6429            ˇThis is a very long line of plain text
 6430            that will be wrapped.
 6431        "},
 6432        plaintext_language.clone(),
 6433        &mut cx,
 6434    );
 6435
 6436    // Test that non-commented code acts as a paragraph boundary within a selection
 6437    assert_rewrap(
 6438        indoc! {"
 6439               «// This is the first long comment block to be wrapped.
 6440               fn my_func(a: u32);
 6441               // This is the second long comment block to be wrapped.ˇ»
 6442           "},
 6443        indoc! {"
 6444               «// This is the first long comment block
 6445               // to be wrapped.
 6446               fn my_func(a: u32);
 6447               // This is the second long comment block
 6448               // to be wrapped.ˇ»
 6449           "},
 6450        rust_language,
 6451        &mut cx,
 6452    );
 6453
 6454    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6455    assert_rewrap(
 6456        indoc! {"
 6457            «ˇThis is a very long line that will be wrapped.
 6458
 6459            This is another paragraph in the same selection.»
 6460
 6461            «\tThis is a very long indented line that will be wrapped.ˇ»
 6462         "},
 6463        indoc! {"
 6464            «ˇThis is a very long line that will be
 6465            wrapped.
 6466
 6467            This is another paragraph in the same
 6468            selection.»
 6469
 6470            «\tThis is a very long indented line
 6471            \tthat will be wrapped.ˇ»
 6472         "},
 6473        plaintext_language,
 6474        &mut cx,
 6475    );
 6476
 6477    // Test that an empty comment line acts as a paragraph boundary
 6478    assert_rewrap(
 6479        indoc! {"
 6480            // ˇThis is a long comment that will be wrapped.
 6481            //
 6482            // And this is another long comment that will also be wrapped.ˇ
 6483         "},
 6484        indoc! {"
 6485            // ˇThis is a long comment that will be
 6486            // wrapped.
 6487            //
 6488            // And this is another long comment that
 6489            // will also be wrapped.ˇ
 6490         "},
 6491        cpp_language,
 6492        &mut cx,
 6493    );
 6494
 6495    #[track_caller]
 6496    fn assert_rewrap(
 6497        unwrapped_text: &str,
 6498        wrapped_text: &str,
 6499        language: Arc<Language>,
 6500        cx: &mut EditorTestContext,
 6501    ) {
 6502        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6503        cx.set_state(unwrapped_text);
 6504        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6505        cx.assert_editor_state(wrapped_text);
 6506    }
 6507}
 6508
 6509#[gpui::test]
 6510async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6511    init_test(cx, |settings| {
 6512        settings.languages.0.extend([(
 6513            "Rust".into(),
 6514            LanguageSettingsContent {
 6515                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6516                preferred_line_length: Some(40),
 6517                ..Default::default()
 6518            },
 6519        )])
 6520    });
 6521
 6522    let mut cx = EditorTestContext::new(cx).await;
 6523
 6524    let rust_lang = Arc::new(
 6525        Language::new(
 6526            LanguageConfig {
 6527                name: "Rust".into(),
 6528                line_comments: vec!["// ".into()],
 6529                block_comment: Some(BlockCommentConfig {
 6530                    start: "/*".into(),
 6531                    end: "*/".into(),
 6532                    prefix: "* ".into(),
 6533                    tab_size: 1,
 6534                }),
 6535                documentation_comment: Some(BlockCommentConfig {
 6536                    start: "/**".into(),
 6537                    end: "*/".into(),
 6538                    prefix: "* ".into(),
 6539                    tab_size: 1,
 6540                }),
 6541
 6542                ..LanguageConfig::default()
 6543            },
 6544            Some(tree_sitter_rust::LANGUAGE.into()),
 6545        )
 6546        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6547        .unwrap(),
 6548    );
 6549
 6550    // regular block comment
 6551    assert_rewrap(
 6552        indoc! {"
 6553            /*
 6554             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6555             */
 6556            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6557        "},
 6558        indoc! {"
 6559            /*
 6560             *ˇ Lorem ipsum dolor sit amet,
 6561             * consectetur adipiscing elit.
 6562             */
 6563            /*
 6564             *ˇ Lorem ipsum dolor sit amet,
 6565             * consectetur adipiscing elit.
 6566             */
 6567        "},
 6568        rust_lang.clone(),
 6569        &mut cx,
 6570    );
 6571
 6572    // indent is respected
 6573    assert_rewrap(
 6574        indoc! {"
 6575            {}
 6576                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6577        "},
 6578        indoc! {"
 6579            {}
 6580                /*
 6581                 *ˇ Lorem ipsum dolor sit amet,
 6582                 * consectetur adipiscing elit.
 6583                 */
 6584        "},
 6585        rust_lang.clone(),
 6586        &mut cx,
 6587    );
 6588
 6589    // short block comments with inline delimiters
 6590    assert_rewrap(
 6591        indoc! {"
 6592            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6593            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6594             */
 6595            /*
 6596             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6597        "},
 6598        indoc! {"
 6599            /*
 6600             *ˇ Lorem ipsum dolor sit amet,
 6601             * consectetur adipiscing elit.
 6602             */
 6603            /*
 6604             *ˇ Lorem ipsum dolor sit amet,
 6605             * consectetur adipiscing elit.
 6606             */
 6607            /*
 6608             *ˇ Lorem ipsum dolor sit amet,
 6609             * consectetur adipiscing elit.
 6610             */
 6611        "},
 6612        rust_lang.clone(),
 6613        &mut cx,
 6614    );
 6615
 6616    // multiline block comment with inline start/end delimiters
 6617    assert_rewrap(
 6618        indoc! {"
 6619            /*ˇ Lorem ipsum dolor sit amet,
 6620             * consectetur adipiscing elit. */
 6621        "},
 6622        indoc! {"
 6623            /*
 6624             *ˇ Lorem ipsum dolor sit amet,
 6625             * consectetur adipiscing elit.
 6626             */
 6627        "},
 6628        rust_lang.clone(),
 6629        &mut cx,
 6630    );
 6631
 6632    // block comment rewrap still respects paragraph bounds
 6633    assert_rewrap(
 6634        indoc! {"
 6635            /*
 6636             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6637             *
 6638             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6639             */
 6640        "},
 6641        indoc! {"
 6642            /*
 6643             *ˇ Lorem ipsum dolor sit amet,
 6644             * consectetur adipiscing elit.
 6645             *
 6646             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6647             */
 6648        "},
 6649        rust_lang.clone(),
 6650        &mut cx,
 6651    );
 6652
 6653    // documentation comments
 6654    assert_rewrap(
 6655        indoc! {"
 6656            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6657            /**
 6658             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6659             */
 6660        "},
 6661        indoc! {"
 6662            /**
 6663             *ˇ Lorem ipsum dolor sit amet,
 6664             * consectetur adipiscing elit.
 6665             */
 6666            /**
 6667             *ˇ Lorem ipsum dolor sit amet,
 6668             * consectetur adipiscing elit.
 6669             */
 6670        "},
 6671        rust_lang.clone(),
 6672        &mut cx,
 6673    );
 6674
 6675    // different, adjacent comments
 6676    assert_rewrap(
 6677        indoc! {"
 6678            /**
 6679             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6680             */
 6681            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6682            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6683        "},
 6684        indoc! {"
 6685            /**
 6686             *ˇ Lorem ipsum dolor sit amet,
 6687             * consectetur adipiscing elit.
 6688             */
 6689            /*
 6690             *ˇ Lorem ipsum dolor sit amet,
 6691             * consectetur adipiscing elit.
 6692             */
 6693            //ˇ Lorem ipsum dolor sit amet,
 6694            // consectetur adipiscing elit.
 6695        "},
 6696        rust_lang.clone(),
 6697        &mut cx,
 6698    );
 6699
 6700    // selection w/ single short block comment
 6701    assert_rewrap(
 6702        indoc! {"
 6703            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6704        "},
 6705        indoc! {"
 6706            «/*
 6707             * Lorem ipsum dolor sit amet,
 6708             * consectetur adipiscing elit.
 6709             */ˇ»
 6710        "},
 6711        rust_lang.clone(),
 6712        &mut cx,
 6713    );
 6714
 6715    // rewrapping a single comment w/ abutting comments
 6716    assert_rewrap(
 6717        indoc! {"
 6718            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6719            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6720        "},
 6721        indoc! {"
 6722            /*
 6723             * ˇLorem ipsum dolor sit amet,
 6724             * consectetur adipiscing elit.
 6725             */
 6726            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6727        "},
 6728        rust_lang.clone(),
 6729        &mut cx,
 6730    );
 6731
 6732    // selection w/ non-abutting short block comments
 6733    assert_rewrap(
 6734        indoc! {"
 6735            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6736
 6737            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6738        "},
 6739        indoc! {"
 6740            «/*
 6741             * Lorem ipsum dolor sit amet,
 6742             * consectetur adipiscing elit.
 6743             */
 6744
 6745            /*
 6746             * Lorem ipsum dolor sit amet,
 6747             * consectetur adipiscing elit.
 6748             */ˇ»
 6749        "},
 6750        rust_lang.clone(),
 6751        &mut cx,
 6752    );
 6753
 6754    // selection of multiline block comments
 6755    assert_rewrap(
 6756        indoc! {"
 6757            «/* Lorem ipsum dolor sit amet,
 6758             * consectetur adipiscing elit. */ˇ»
 6759        "},
 6760        indoc! {"
 6761            «/*
 6762             * Lorem ipsum dolor sit amet,
 6763             * consectetur adipiscing elit.
 6764             */ˇ»
 6765        "},
 6766        rust_lang.clone(),
 6767        &mut cx,
 6768    );
 6769
 6770    // partial selection of multiline block comments
 6771    assert_rewrap(
 6772        indoc! {"
 6773            «/* Lorem ipsum dolor sit amet,ˇ»
 6774             * consectetur adipiscing elit. */
 6775            /* Lorem ipsum dolor sit amet,
 6776             «* consectetur adipiscing elit. */ˇ»
 6777        "},
 6778        indoc! {"
 6779            «/*
 6780             * Lorem ipsum dolor sit amet,ˇ»
 6781             * consectetur adipiscing elit. */
 6782            /* Lorem ipsum dolor sit amet,
 6783             «* consectetur adipiscing elit.
 6784             */ˇ»
 6785        "},
 6786        rust_lang.clone(),
 6787        &mut cx,
 6788    );
 6789
 6790    // selection w/ abutting short block comments
 6791    // TODO: should not be combined; should rewrap as 2 comments
 6792    assert_rewrap(
 6793        indoc! {"
 6794            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6795            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6796        "},
 6797        // desired behavior:
 6798        // indoc! {"
 6799        //     «/*
 6800        //      * Lorem ipsum dolor sit amet,
 6801        //      * consectetur adipiscing elit.
 6802        //      */
 6803        //     /*
 6804        //      * Lorem ipsum dolor sit amet,
 6805        //      * consectetur adipiscing elit.
 6806        //      */ˇ»
 6807        // "},
 6808        // actual behaviour:
 6809        indoc! {"
 6810            «/*
 6811             * Lorem ipsum dolor sit amet,
 6812             * consectetur adipiscing elit. Lorem
 6813             * ipsum dolor sit amet, consectetur
 6814             * adipiscing elit.
 6815             */ˇ»
 6816        "},
 6817        rust_lang.clone(),
 6818        &mut cx,
 6819    );
 6820
 6821    // TODO: same as above, but with delimiters on separate line
 6822    // assert_rewrap(
 6823    //     indoc! {"
 6824    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6825    //          */
 6826    //         /*
 6827    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6828    //     "},
 6829    //     // desired:
 6830    //     // indoc! {"
 6831    //     //     «/*
 6832    //     //      * Lorem ipsum dolor sit amet,
 6833    //     //      * consectetur adipiscing elit.
 6834    //     //      */
 6835    //     //     /*
 6836    //     //      * Lorem ipsum dolor sit amet,
 6837    //     //      * consectetur adipiscing elit.
 6838    //     //      */ˇ»
 6839    //     // "},
 6840    //     // actual: (but with trailing w/s on the empty lines)
 6841    //     indoc! {"
 6842    //         «/*
 6843    //          * Lorem ipsum dolor sit amet,
 6844    //          * consectetur adipiscing elit.
 6845    //          *
 6846    //          */
 6847    //         /*
 6848    //          *
 6849    //          * Lorem ipsum dolor sit amet,
 6850    //          * consectetur adipiscing elit.
 6851    //          */ˇ»
 6852    //     "},
 6853    //     rust_lang.clone(),
 6854    //     &mut cx,
 6855    // );
 6856
 6857    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6858    assert_rewrap(
 6859        indoc! {"
 6860            /*
 6861             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6862             */
 6863            /*
 6864             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6865            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6866        "},
 6867        // desired:
 6868        // indoc! {"
 6869        //     /*
 6870        //      *ˇ Lorem ipsum dolor sit amet,
 6871        //      * consectetur adipiscing elit.
 6872        //      */
 6873        //     /*
 6874        //      *ˇ Lorem ipsum dolor sit amet,
 6875        //      * consectetur adipiscing elit.
 6876        //      */
 6877        //     /*
 6878        //      *ˇ Lorem ipsum dolor sit amet
 6879        //      */ /* consectetur adipiscing elit. */
 6880        // "},
 6881        // actual:
 6882        indoc! {"
 6883            /*
 6884             //ˇ Lorem ipsum dolor sit amet,
 6885             // consectetur adipiscing elit.
 6886             */
 6887            /*
 6888             * //ˇ Lorem ipsum dolor sit amet,
 6889             * consectetur adipiscing elit.
 6890             */
 6891            /*
 6892             *ˇ Lorem ipsum dolor sit amet */ /*
 6893             * consectetur adipiscing elit.
 6894             */
 6895        "},
 6896        rust_lang,
 6897        &mut cx,
 6898    );
 6899
 6900    #[track_caller]
 6901    fn assert_rewrap(
 6902        unwrapped_text: &str,
 6903        wrapped_text: &str,
 6904        language: Arc<Language>,
 6905        cx: &mut EditorTestContext,
 6906    ) {
 6907        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6908        cx.set_state(unwrapped_text);
 6909        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6910        cx.assert_editor_state(wrapped_text);
 6911    }
 6912}
 6913
 6914#[gpui::test]
 6915async fn test_hard_wrap(cx: &mut TestAppContext) {
 6916    init_test(cx, |_| {});
 6917    let mut cx = EditorTestContext::new(cx).await;
 6918
 6919    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6920    cx.update_editor(|editor, _, cx| {
 6921        editor.set_hard_wrap(Some(14), cx);
 6922    });
 6923
 6924    cx.set_state(indoc!(
 6925        "
 6926        one two three ˇ
 6927        "
 6928    ));
 6929    cx.simulate_input("four");
 6930    cx.run_until_parked();
 6931
 6932    cx.assert_editor_state(indoc!(
 6933        "
 6934        one two three
 6935        fourˇ
 6936        "
 6937    ));
 6938
 6939    cx.update_editor(|editor, window, cx| {
 6940        editor.newline(&Default::default(), window, cx);
 6941    });
 6942    cx.run_until_parked();
 6943    cx.assert_editor_state(indoc!(
 6944        "
 6945        one two three
 6946        four
 6947        ˇ
 6948        "
 6949    ));
 6950
 6951    cx.simulate_input("five");
 6952    cx.run_until_parked();
 6953    cx.assert_editor_state(indoc!(
 6954        "
 6955        one two three
 6956        four
 6957        fiveˇ
 6958        "
 6959    ));
 6960
 6961    cx.update_editor(|editor, window, cx| {
 6962        editor.newline(&Default::default(), window, cx);
 6963    });
 6964    cx.run_until_parked();
 6965    cx.simulate_input("# ");
 6966    cx.run_until_parked();
 6967    cx.assert_editor_state(indoc!(
 6968        "
 6969        one two three
 6970        four
 6971        five
 6972        # ˇ
 6973        "
 6974    ));
 6975
 6976    cx.update_editor(|editor, window, cx| {
 6977        editor.newline(&Default::default(), window, cx);
 6978    });
 6979    cx.run_until_parked();
 6980    cx.assert_editor_state(indoc!(
 6981        "
 6982        one two three
 6983        four
 6984        five
 6985        #\x20
 6986 6987        "
 6988    ));
 6989
 6990    cx.simulate_input(" 6");
 6991    cx.run_until_parked();
 6992    cx.assert_editor_state(indoc!(
 6993        "
 6994        one two three
 6995        four
 6996        five
 6997        #
 6998        # 6ˇ
 6999        "
 7000    ));
 7001}
 7002
 7003#[gpui::test]
 7004async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7005    init_test(cx, |_| {});
 7006
 7007    let mut cx = EditorTestContext::new(cx).await;
 7008
 7009    cx.set_state(indoc! {"The quick brownˇ"});
 7010    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7011    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7012
 7013    cx.set_state(indoc! {"The emacs foxˇ"});
 7014    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7015    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7016
 7017    cx.set_state(indoc! {"
 7018        The quick« brownˇ»
 7019        fox jumps overˇ
 7020        the lazy dog"});
 7021    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7022    cx.assert_editor_state(indoc! {"
 7023        The quickˇ
 7024        ˇthe lazy dog"});
 7025
 7026    cx.set_state(indoc! {"
 7027        The quick« brownˇ»
 7028        fox jumps overˇ
 7029        the lazy dog"});
 7030    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7031    cx.assert_editor_state(indoc! {"
 7032        The quickˇ
 7033        fox jumps overˇthe lazy dog"});
 7034
 7035    cx.set_state(indoc! {"
 7036        The quick« brownˇ»
 7037        fox jumps overˇ
 7038        the lazy dog"});
 7039    cx.update_editor(|e, window, cx| {
 7040        e.cut_to_end_of_line(
 7041            &CutToEndOfLine {
 7042                stop_at_newlines: true,
 7043            },
 7044            window,
 7045            cx,
 7046        )
 7047    });
 7048    cx.assert_editor_state(indoc! {"
 7049        The quickˇ
 7050        fox jumps overˇ
 7051        the lazy dog"});
 7052
 7053    cx.set_state(indoc! {"
 7054        The quick« brownˇ»
 7055        fox jumps overˇ
 7056        the lazy dog"});
 7057    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7058    cx.assert_editor_state(indoc! {"
 7059        The quickˇ
 7060        fox jumps overˇthe lazy dog"});
 7061}
 7062
 7063#[gpui::test]
 7064async fn test_clipboard(cx: &mut TestAppContext) {
 7065    init_test(cx, |_| {});
 7066
 7067    let mut cx = EditorTestContext::new(cx).await;
 7068
 7069    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7070    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7071    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7072
 7073    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7074    cx.set_state("two ˇfour ˇsix ˇ");
 7075    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7076    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7077
 7078    // Paste again but with only two cursors. Since the number of cursors doesn't
 7079    // match the number of slices in the clipboard, the entire clipboard text
 7080    // is pasted at each cursor.
 7081    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7082    cx.update_editor(|e, window, cx| {
 7083        e.handle_input("( ", window, cx);
 7084        e.paste(&Paste, window, cx);
 7085        e.handle_input(") ", window, cx);
 7086    });
 7087    cx.assert_editor_state(
 7088        &([
 7089            "( one✅ ",
 7090            "three ",
 7091            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7092            "three ",
 7093            "five ) ˇ",
 7094        ]
 7095        .join("\n")),
 7096    );
 7097
 7098    // Cut with three selections, one of which is full-line.
 7099    cx.set_state(indoc! {"
 7100        1«2ˇ»3
 7101        4ˇ567
 7102        «8ˇ»9"});
 7103    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7104    cx.assert_editor_state(indoc! {"
 7105        1ˇ3
 7106        ˇ9"});
 7107
 7108    // Paste with three selections, noticing how the copied selection that was full-line
 7109    // gets inserted before the second cursor.
 7110    cx.set_state(indoc! {"
 7111        1ˇ3
 7112 7113        «oˇ»ne"});
 7114    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7115    cx.assert_editor_state(indoc! {"
 7116        12ˇ3
 7117        4567
 7118 7119        8ˇne"});
 7120
 7121    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7122    cx.set_state(indoc! {"
 7123        The quick brown
 7124        fox juˇmps over
 7125        the lazy dog"});
 7126    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7127    assert_eq!(
 7128        cx.read_from_clipboard()
 7129            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7130        Some("fox jumps over\n".to_string())
 7131    );
 7132
 7133    // Paste with three selections, noticing how the copied full-line selection is inserted
 7134    // before the empty selections but replaces the selection that is non-empty.
 7135    cx.set_state(indoc! {"
 7136        Tˇhe quick brown
 7137        «foˇ»x jumps over
 7138        tˇhe lazy dog"});
 7139    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7140    cx.assert_editor_state(indoc! {"
 7141        fox jumps over
 7142        Tˇhe quick brown
 7143        fox jumps over
 7144        ˇx jumps over
 7145        fox jumps over
 7146        tˇhe lazy dog"});
 7147}
 7148
 7149#[gpui::test]
 7150async fn test_copy_trim(cx: &mut TestAppContext) {
 7151    init_test(cx, |_| {});
 7152
 7153    let mut cx = EditorTestContext::new(cx).await;
 7154    cx.set_state(
 7155        r#"            «for selection in selections.iter() {
 7156            let mut start = selection.start;
 7157            let mut end = selection.end;
 7158            let is_entire_line = selection.is_empty();
 7159            if is_entire_line {
 7160                start = Point::new(start.row, 0);ˇ»
 7161                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7162            }
 7163        "#,
 7164    );
 7165    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7166    assert_eq!(
 7167        cx.read_from_clipboard()
 7168            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7169        Some(
 7170            "for selection in selections.iter() {
 7171            let mut start = selection.start;
 7172            let mut end = selection.end;
 7173            let is_entire_line = selection.is_empty();
 7174            if is_entire_line {
 7175                start = Point::new(start.row, 0);"
 7176                .to_string()
 7177        ),
 7178        "Regular copying preserves all indentation selected",
 7179    );
 7180    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7181    assert_eq!(
 7182        cx.read_from_clipboard()
 7183            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7184        Some(
 7185            "for selection in selections.iter() {
 7186let mut start = selection.start;
 7187let mut end = selection.end;
 7188let is_entire_line = selection.is_empty();
 7189if is_entire_line {
 7190    start = Point::new(start.row, 0);"
 7191                .to_string()
 7192        ),
 7193        "Copying with stripping should strip all leading whitespaces"
 7194    );
 7195
 7196    cx.set_state(
 7197        r#"       «     for selection in selections.iter() {
 7198            let mut start = selection.start;
 7199            let mut end = selection.end;
 7200            let is_entire_line = selection.is_empty();
 7201            if is_entire_line {
 7202                start = Point::new(start.row, 0);ˇ»
 7203                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7204            }
 7205        "#,
 7206    );
 7207    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7208    assert_eq!(
 7209        cx.read_from_clipboard()
 7210            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7211        Some(
 7212            "     for selection in selections.iter() {
 7213            let mut start = selection.start;
 7214            let mut end = selection.end;
 7215            let is_entire_line = selection.is_empty();
 7216            if is_entire_line {
 7217                start = Point::new(start.row, 0);"
 7218                .to_string()
 7219        ),
 7220        "Regular copying preserves all indentation selected",
 7221    );
 7222    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7223    assert_eq!(
 7224        cx.read_from_clipboard()
 7225            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7226        Some(
 7227            "for selection in selections.iter() {
 7228let mut start = selection.start;
 7229let mut end = selection.end;
 7230let is_entire_line = selection.is_empty();
 7231if is_entire_line {
 7232    start = Point::new(start.row, 0);"
 7233                .to_string()
 7234        ),
 7235        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7236    );
 7237
 7238    cx.set_state(
 7239        r#"       «ˇ     for selection in selections.iter() {
 7240            let mut start = selection.start;
 7241            let mut end = selection.end;
 7242            let is_entire_line = selection.is_empty();
 7243            if is_entire_line {
 7244                start = Point::new(start.row, 0);»
 7245                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7246            }
 7247        "#,
 7248    );
 7249    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7250    assert_eq!(
 7251        cx.read_from_clipboard()
 7252            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7253        Some(
 7254            "     for selection in selections.iter() {
 7255            let mut start = selection.start;
 7256            let mut end = selection.end;
 7257            let is_entire_line = selection.is_empty();
 7258            if is_entire_line {
 7259                start = Point::new(start.row, 0);"
 7260                .to_string()
 7261        ),
 7262        "Regular copying for reverse selection works the same",
 7263    );
 7264    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7265    assert_eq!(
 7266        cx.read_from_clipboard()
 7267            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7268        Some(
 7269            "for selection in selections.iter() {
 7270let mut start = selection.start;
 7271let mut end = selection.end;
 7272let is_entire_line = selection.is_empty();
 7273if is_entire_line {
 7274    start = Point::new(start.row, 0);"
 7275                .to_string()
 7276        ),
 7277        "Copying with stripping for reverse selection works the same"
 7278    );
 7279
 7280    cx.set_state(
 7281        r#"            for selection «in selections.iter() {
 7282            let mut start = selection.start;
 7283            let mut end = selection.end;
 7284            let is_entire_line = selection.is_empty();
 7285            if is_entire_line {
 7286                start = Point::new(start.row, 0);ˇ»
 7287                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7288            }
 7289        "#,
 7290    );
 7291    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7292    assert_eq!(
 7293        cx.read_from_clipboard()
 7294            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7295        Some(
 7296            "in selections.iter() {
 7297            let mut start = selection.start;
 7298            let mut end = selection.end;
 7299            let is_entire_line = selection.is_empty();
 7300            if is_entire_line {
 7301                start = Point::new(start.row, 0);"
 7302                .to_string()
 7303        ),
 7304        "When selecting past the indent, the copying works as usual",
 7305    );
 7306    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7307    assert_eq!(
 7308        cx.read_from_clipboard()
 7309            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7310        Some(
 7311            "in selections.iter() {
 7312            let mut start = selection.start;
 7313            let mut end = selection.end;
 7314            let is_entire_line = selection.is_empty();
 7315            if is_entire_line {
 7316                start = Point::new(start.row, 0);"
 7317                .to_string()
 7318        ),
 7319        "When selecting past the indent, nothing is trimmed"
 7320    );
 7321
 7322    cx.set_state(
 7323        r#"            «for selection in selections.iter() {
 7324            let mut start = selection.start;
 7325
 7326            let mut end = selection.end;
 7327            let is_entire_line = selection.is_empty();
 7328            if is_entire_line {
 7329                start = Point::new(start.row, 0);
 7330ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7331            }
 7332        "#,
 7333    );
 7334    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7335    assert_eq!(
 7336        cx.read_from_clipboard()
 7337            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7338        Some(
 7339            "for selection in selections.iter() {
 7340let mut start = selection.start;
 7341
 7342let mut end = selection.end;
 7343let is_entire_line = selection.is_empty();
 7344if is_entire_line {
 7345    start = Point::new(start.row, 0);
 7346"
 7347            .to_string()
 7348        ),
 7349        "Copying with stripping should ignore empty lines"
 7350    );
 7351}
 7352
 7353#[gpui::test]
 7354async fn test_paste_multiline(cx: &mut TestAppContext) {
 7355    init_test(cx, |_| {});
 7356
 7357    let mut cx = EditorTestContext::new(cx).await;
 7358    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7359
 7360    // Cut an indented block, without the leading whitespace.
 7361    cx.set_state(indoc! {"
 7362        const a: B = (
 7363            c(),
 7364            «d(
 7365                e,
 7366                f
 7367            )ˇ»
 7368        );
 7369    "});
 7370    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7371    cx.assert_editor_state(indoc! {"
 7372        const a: B = (
 7373            c(),
 7374            ˇ
 7375        );
 7376    "});
 7377
 7378    // Paste it at the same position.
 7379    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7380    cx.assert_editor_state(indoc! {"
 7381        const a: B = (
 7382            c(),
 7383            d(
 7384                e,
 7385                f
 7386 7387        );
 7388    "});
 7389
 7390    // Paste it at a line with a lower indent level.
 7391    cx.set_state(indoc! {"
 7392        ˇ
 7393        const a: B = (
 7394            c(),
 7395        );
 7396    "});
 7397    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7398    cx.assert_editor_state(indoc! {"
 7399        d(
 7400            e,
 7401            f
 7402 7403        const a: B = (
 7404            c(),
 7405        );
 7406    "});
 7407
 7408    // Cut an indented block, with the leading whitespace.
 7409    cx.set_state(indoc! {"
 7410        const a: B = (
 7411            c(),
 7412        «    d(
 7413                e,
 7414                f
 7415            )
 7416        ˇ»);
 7417    "});
 7418    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7419    cx.assert_editor_state(indoc! {"
 7420        const a: B = (
 7421            c(),
 7422        ˇ);
 7423    "});
 7424
 7425    // Paste it at the same position.
 7426    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7427    cx.assert_editor_state(indoc! {"
 7428        const a: B = (
 7429            c(),
 7430            d(
 7431                e,
 7432                f
 7433            )
 7434        ˇ);
 7435    "});
 7436
 7437    // Paste it at a line with a higher indent level.
 7438    cx.set_state(indoc! {"
 7439        const a: B = (
 7440            c(),
 7441            d(
 7442                e,
 7443 7444            )
 7445        );
 7446    "});
 7447    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7448    cx.assert_editor_state(indoc! {"
 7449        const a: B = (
 7450            c(),
 7451            d(
 7452                e,
 7453                f    d(
 7454                    e,
 7455                    f
 7456                )
 7457        ˇ
 7458            )
 7459        );
 7460    "});
 7461
 7462    // Copy an indented block, starting mid-line
 7463    cx.set_state(indoc! {"
 7464        const a: B = (
 7465            c(),
 7466            somethin«g(
 7467                e,
 7468                f
 7469            )ˇ»
 7470        );
 7471    "});
 7472    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7473
 7474    // Paste it on a line with a lower indent level
 7475    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7476    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7477    cx.assert_editor_state(indoc! {"
 7478        const a: B = (
 7479            c(),
 7480            something(
 7481                e,
 7482                f
 7483            )
 7484        );
 7485        g(
 7486            e,
 7487            f
 7488"});
 7489}
 7490
 7491#[gpui::test]
 7492async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7493    init_test(cx, |_| {});
 7494
 7495    cx.write_to_clipboard(ClipboardItem::new_string(
 7496        "    d(\n        e\n    );\n".into(),
 7497    ));
 7498
 7499    let mut cx = EditorTestContext::new(cx).await;
 7500    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7501
 7502    cx.set_state(indoc! {"
 7503        fn a() {
 7504            b();
 7505            if c() {
 7506                ˇ
 7507            }
 7508        }
 7509    "});
 7510
 7511    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7512    cx.assert_editor_state(indoc! {"
 7513        fn a() {
 7514            b();
 7515            if c() {
 7516                d(
 7517                    e
 7518                );
 7519        ˇ
 7520            }
 7521        }
 7522    "});
 7523
 7524    cx.set_state(indoc! {"
 7525        fn a() {
 7526            b();
 7527            ˇ
 7528        }
 7529    "});
 7530
 7531    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7532    cx.assert_editor_state(indoc! {"
 7533        fn a() {
 7534            b();
 7535            d(
 7536                e
 7537            );
 7538        ˇ
 7539        }
 7540    "});
 7541}
 7542
 7543#[gpui::test]
 7544fn test_select_all(cx: &mut TestAppContext) {
 7545    init_test(cx, |_| {});
 7546
 7547    let editor = cx.add_window(|window, cx| {
 7548        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7549        build_editor(buffer, window, cx)
 7550    });
 7551    _ = editor.update(cx, |editor, window, cx| {
 7552        editor.select_all(&SelectAll, window, cx);
 7553        assert_eq!(
 7554            display_ranges(editor, cx),
 7555            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7556        );
 7557    });
 7558}
 7559
 7560#[gpui::test]
 7561fn test_select_line(cx: &mut TestAppContext) {
 7562    init_test(cx, |_| {});
 7563
 7564    let editor = cx.add_window(|window, cx| {
 7565        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7566        build_editor(buffer, window, cx)
 7567    });
 7568    _ = editor.update(cx, |editor, window, cx| {
 7569        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7570            s.select_display_ranges([
 7571                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7574                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7575            ])
 7576        });
 7577        editor.select_line(&SelectLine, window, cx);
 7578        assert_eq!(
 7579            display_ranges(editor, cx),
 7580            vec![
 7581                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7582                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7583            ]
 7584        );
 7585    });
 7586
 7587    _ = editor.update(cx, |editor, window, cx| {
 7588        editor.select_line(&SelectLine, window, cx);
 7589        assert_eq!(
 7590            display_ranges(editor, cx),
 7591            vec![
 7592                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7593                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7594            ]
 7595        );
 7596    });
 7597
 7598    _ = editor.update(cx, |editor, window, cx| {
 7599        editor.select_line(&SelectLine, window, cx);
 7600        assert_eq!(
 7601            display_ranges(editor, cx),
 7602            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7603        );
 7604    });
 7605}
 7606
 7607#[gpui::test]
 7608async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7609    init_test(cx, |_| {});
 7610    let mut cx = EditorTestContext::new(cx).await;
 7611
 7612    #[track_caller]
 7613    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7614        cx.set_state(initial_state);
 7615        cx.update_editor(|e, window, cx| {
 7616            e.split_selection_into_lines(&Default::default(), window, cx)
 7617        });
 7618        cx.assert_editor_state(expected_state);
 7619    }
 7620
 7621    // Selection starts and ends at the middle of lines, left-to-right
 7622    test(
 7623        &mut cx,
 7624        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7625        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7626    );
 7627    // Same thing, right-to-left
 7628    test(
 7629        &mut cx,
 7630        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7631        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7632    );
 7633
 7634    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7635    test(
 7636        &mut cx,
 7637        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7638        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7639    );
 7640    // Same thing, right-to-left
 7641    test(
 7642        &mut cx,
 7643        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7644        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7645    );
 7646
 7647    // Whole buffer, left-to-right, last line ends with newline
 7648    test(
 7649        &mut cx,
 7650        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7651        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7652    );
 7653    // Same thing, right-to-left
 7654    test(
 7655        &mut cx,
 7656        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7657        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7658    );
 7659
 7660    // Starts at the end of a line, ends at the start of another
 7661    test(
 7662        &mut cx,
 7663        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7664        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7665    );
 7666}
 7667
 7668#[gpui::test]
 7669async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7670    init_test(cx, |_| {});
 7671
 7672    let editor = cx.add_window(|window, cx| {
 7673        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7674        build_editor(buffer, window, cx)
 7675    });
 7676
 7677    // setup
 7678    _ = editor.update(cx, |editor, window, cx| {
 7679        editor.fold_creases(
 7680            vec![
 7681                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7682                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7683                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7684            ],
 7685            true,
 7686            window,
 7687            cx,
 7688        );
 7689        assert_eq!(
 7690            editor.display_text(cx),
 7691            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7692        );
 7693    });
 7694
 7695    _ = editor.update(cx, |editor, window, cx| {
 7696        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7697            s.select_display_ranges([
 7698                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7699                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7700                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7701                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7702            ])
 7703        });
 7704        editor.split_selection_into_lines(&Default::default(), window, cx);
 7705        assert_eq!(
 7706            editor.display_text(cx),
 7707            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7708        );
 7709    });
 7710    EditorTestContext::for_editor(editor, cx)
 7711        .await
 7712        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7713
 7714    _ = editor.update(cx, |editor, window, cx| {
 7715        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7716            s.select_display_ranges([
 7717                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7718            ])
 7719        });
 7720        editor.split_selection_into_lines(&Default::default(), window, cx);
 7721        assert_eq!(
 7722            editor.display_text(cx),
 7723            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7724        );
 7725        assert_eq!(
 7726            display_ranges(editor, cx),
 7727            [
 7728                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7729                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7730                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7731                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7732                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7733                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7734                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7735            ]
 7736        );
 7737    });
 7738    EditorTestContext::for_editor(editor, cx)
 7739        .await
 7740        .assert_editor_state(
 7741            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7742        );
 7743}
 7744
 7745#[gpui::test]
 7746async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7747    init_test(cx, |_| {});
 7748
 7749    let mut cx = EditorTestContext::new(cx).await;
 7750
 7751    cx.set_state(indoc!(
 7752        r#"abc
 7753           defˇghi
 7754
 7755           jk
 7756           nlmo
 7757           "#
 7758    ));
 7759
 7760    cx.update_editor(|editor, window, cx| {
 7761        editor.add_selection_above(&Default::default(), window, cx);
 7762    });
 7763
 7764    cx.assert_editor_state(indoc!(
 7765        r#"abcˇ
 7766           defˇghi
 7767
 7768           jk
 7769           nlmo
 7770           "#
 7771    ));
 7772
 7773    cx.update_editor(|editor, window, cx| {
 7774        editor.add_selection_above(&Default::default(), window, cx);
 7775    });
 7776
 7777    cx.assert_editor_state(indoc!(
 7778        r#"abcˇ
 7779            defˇghi
 7780
 7781            jk
 7782            nlmo
 7783            "#
 7784    ));
 7785
 7786    cx.update_editor(|editor, window, cx| {
 7787        editor.add_selection_below(&Default::default(), window, cx);
 7788    });
 7789
 7790    cx.assert_editor_state(indoc!(
 7791        r#"abc
 7792           defˇghi
 7793
 7794           jk
 7795           nlmo
 7796           "#
 7797    ));
 7798
 7799    cx.update_editor(|editor, window, cx| {
 7800        editor.undo_selection(&Default::default(), window, cx);
 7801    });
 7802
 7803    cx.assert_editor_state(indoc!(
 7804        r#"abcˇ
 7805           defˇghi
 7806
 7807           jk
 7808           nlmo
 7809           "#
 7810    ));
 7811
 7812    cx.update_editor(|editor, window, cx| {
 7813        editor.redo_selection(&Default::default(), window, cx);
 7814    });
 7815
 7816    cx.assert_editor_state(indoc!(
 7817        r#"abc
 7818           defˇghi
 7819
 7820           jk
 7821           nlmo
 7822           "#
 7823    ));
 7824
 7825    cx.update_editor(|editor, window, cx| {
 7826        editor.add_selection_below(&Default::default(), window, cx);
 7827    });
 7828
 7829    cx.assert_editor_state(indoc!(
 7830        r#"abc
 7831           defˇghi
 7832           ˇ
 7833           jk
 7834           nlmo
 7835           "#
 7836    ));
 7837
 7838    cx.update_editor(|editor, window, cx| {
 7839        editor.add_selection_below(&Default::default(), window, cx);
 7840    });
 7841
 7842    cx.assert_editor_state(indoc!(
 7843        r#"abc
 7844           defˇghi
 7845           ˇ
 7846           jkˇ
 7847           nlmo
 7848           "#
 7849    ));
 7850
 7851    cx.update_editor(|editor, window, cx| {
 7852        editor.add_selection_below(&Default::default(), window, cx);
 7853    });
 7854
 7855    cx.assert_editor_state(indoc!(
 7856        r#"abc
 7857           defˇghi
 7858           ˇ
 7859           jkˇ
 7860           nlmˇo
 7861           "#
 7862    ));
 7863
 7864    cx.update_editor(|editor, window, cx| {
 7865        editor.add_selection_below(&Default::default(), window, cx);
 7866    });
 7867
 7868    cx.assert_editor_state(indoc!(
 7869        r#"abc
 7870           defˇghi
 7871           ˇ
 7872           jkˇ
 7873           nlmˇo
 7874           ˇ"#
 7875    ));
 7876
 7877    // change selections
 7878    cx.set_state(indoc!(
 7879        r#"abc
 7880           def«ˇg»hi
 7881
 7882           jk
 7883           nlmo
 7884           "#
 7885    ));
 7886
 7887    cx.update_editor(|editor, window, cx| {
 7888        editor.add_selection_below(&Default::default(), window, cx);
 7889    });
 7890
 7891    cx.assert_editor_state(indoc!(
 7892        r#"abc
 7893           def«ˇg»hi
 7894
 7895           jk
 7896           nlm«ˇo»
 7897           "#
 7898    ));
 7899
 7900    cx.update_editor(|editor, window, cx| {
 7901        editor.add_selection_below(&Default::default(), window, cx);
 7902    });
 7903
 7904    cx.assert_editor_state(indoc!(
 7905        r#"abc
 7906           def«ˇg»hi
 7907
 7908           jk
 7909           nlm«ˇo»
 7910           "#
 7911    ));
 7912
 7913    cx.update_editor(|editor, window, cx| {
 7914        editor.add_selection_above(&Default::default(), window, cx);
 7915    });
 7916
 7917    cx.assert_editor_state(indoc!(
 7918        r#"abc
 7919           def«ˇg»hi
 7920
 7921           jk
 7922           nlmo
 7923           "#
 7924    ));
 7925
 7926    cx.update_editor(|editor, window, cx| {
 7927        editor.add_selection_above(&Default::default(), window, cx);
 7928    });
 7929
 7930    cx.assert_editor_state(indoc!(
 7931        r#"abc
 7932           def«ˇg»hi
 7933
 7934           jk
 7935           nlmo
 7936           "#
 7937    ));
 7938
 7939    // Change selections again
 7940    cx.set_state(indoc!(
 7941        r#"a«bc
 7942           defgˇ»hi
 7943
 7944           jk
 7945           nlmo
 7946           "#
 7947    ));
 7948
 7949    cx.update_editor(|editor, window, cx| {
 7950        editor.add_selection_below(&Default::default(), window, cx);
 7951    });
 7952
 7953    cx.assert_editor_state(indoc!(
 7954        r#"a«bcˇ»
 7955           d«efgˇ»hi
 7956
 7957           j«kˇ»
 7958           nlmo
 7959           "#
 7960    ));
 7961
 7962    cx.update_editor(|editor, window, cx| {
 7963        editor.add_selection_below(&Default::default(), window, cx);
 7964    });
 7965    cx.assert_editor_state(indoc!(
 7966        r#"a«bcˇ»
 7967           d«efgˇ»hi
 7968
 7969           j«kˇ»
 7970           n«lmoˇ»
 7971           "#
 7972    ));
 7973    cx.update_editor(|editor, window, cx| {
 7974        editor.add_selection_above(&Default::default(), window, cx);
 7975    });
 7976
 7977    cx.assert_editor_state(indoc!(
 7978        r#"a«bcˇ»
 7979           d«efgˇ»hi
 7980
 7981           j«kˇ»
 7982           nlmo
 7983           "#
 7984    ));
 7985
 7986    // Change selections again
 7987    cx.set_state(indoc!(
 7988        r#"abc
 7989           d«ˇefghi
 7990
 7991           jk
 7992           nlm»o
 7993           "#
 7994    ));
 7995
 7996    cx.update_editor(|editor, window, cx| {
 7997        editor.add_selection_above(&Default::default(), window, cx);
 7998    });
 7999
 8000    cx.assert_editor_state(indoc!(
 8001        r#"a«ˇbc»
 8002           d«ˇef»ghi
 8003
 8004           j«ˇk»
 8005           n«ˇlm»o
 8006           "#
 8007    ));
 8008
 8009    cx.update_editor(|editor, window, cx| {
 8010        editor.add_selection_below(&Default::default(), window, cx);
 8011    });
 8012
 8013    cx.assert_editor_state(indoc!(
 8014        r#"abc
 8015           d«ˇef»ghi
 8016
 8017           j«ˇk»
 8018           n«ˇlm»o
 8019           "#
 8020    ));
 8021}
 8022
 8023#[gpui::test]
 8024async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8025    init_test(cx, |_| {});
 8026    let mut cx = EditorTestContext::new(cx).await;
 8027
 8028    cx.set_state(indoc!(
 8029        r#"line onˇe
 8030           liˇne two
 8031           line three
 8032           line four"#
 8033    ));
 8034
 8035    cx.update_editor(|editor, window, cx| {
 8036        editor.add_selection_below(&Default::default(), window, cx);
 8037    });
 8038
 8039    // test multiple cursors expand in the same direction
 8040    cx.assert_editor_state(indoc!(
 8041        r#"line onˇe
 8042           liˇne twˇo
 8043           liˇne three
 8044           line four"#
 8045    ));
 8046
 8047    cx.update_editor(|editor, window, cx| {
 8048        editor.add_selection_below(&Default::default(), window, cx);
 8049    });
 8050
 8051    cx.update_editor(|editor, window, cx| {
 8052        editor.add_selection_below(&Default::default(), window, cx);
 8053    });
 8054
 8055    // test multiple cursors expand below overflow
 8056    cx.assert_editor_state(indoc!(
 8057        r#"line onˇe
 8058           liˇne twˇo
 8059           liˇne thˇree
 8060           liˇne foˇur"#
 8061    ));
 8062
 8063    cx.update_editor(|editor, window, cx| {
 8064        editor.add_selection_above(&Default::default(), window, cx);
 8065    });
 8066
 8067    // test multiple cursors retrieves back correctly
 8068    cx.assert_editor_state(indoc!(
 8069        r#"line onˇe
 8070           liˇne twˇo
 8071           liˇne thˇree
 8072           line four"#
 8073    ));
 8074
 8075    cx.update_editor(|editor, window, cx| {
 8076        editor.add_selection_above(&Default::default(), window, cx);
 8077    });
 8078
 8079    cx.update_editor(|editor, window, cx| {
 8080        editor.add_selection_above(&Default::default(), window, cx);
 8081    });
 8082
 8083    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8084    cx.assert_editor_state(indoc!(
 8085        r#"liˇne onˇe
 8086           liˇne two
 8087           line three
 8088           line four"#
 8089    ));
 8090
 8091    cx.update_editor(|editor, window, cx| {
 8092        editor.undo_selection(&Default::default(), window, cx);
 8093    });
 8094
 8095    // test undo
 8096    cx.assert_editor_state(indoc!(
 8097        r#"line onˇe
 8098           liˇne twˇo
 8099           line three
 8100           line four"#
 8101    ));
 8102
 8103    cx.update_editor(|editor, window, cx| {
 8104        editor.redo_selection(&Default::default(), window, cx);
 8105    });
 8106
 8107    // test redo
 8108    cx.assert_editor_state(indoc!(
 8109        r#"liˇne onˇe
 8110           liˇne two
 8111           line three
 8112           line four"#
 8113    ));
 8114
 8115    cx.set_state(indoc!(
 8116        r#"abcd
 8117           ef«ghˇ»
 8118           ijkl
 8119           «mˇ»nop"#
 8120    ));
 8121
 8122    cx.update_editor(|editor, window, cx| {
 8123        editor.add_selection_above(&Default::default(), window, cx);
 8124    });
 8125
 8126    // test multiple selections expand in the same direction
 8127    cx.assert_editor_state(indoc!(
 8128        r#"ab«cdˇ»
 8129           ef«ghˇ»
 8130           «iˇ»jkl
 8131           «mˇ»nop"#
 8132    ));
 8133
 8134    cx.update_editor(|editor, window, cx| {
 8135        editor.add_selection_above(&Default::default(), window, cx);
 8136    });
 8137
 8138    // test multiple selection upward overflow
 8139    cx.assert_editor_state(indoc!(
 8140        r#"ab«cdˇ»
 8141           «eˇ»f«ghˇ»
 8142           «iˇ»jkl
 8143           «mˇ»nop"#
 8144    ));
 8145
 8146    cx.update_editor(|editor, window, cx| {
 8147        editor.add_selection_below(&Default::default(), window, cx);
 8148    });
 8149
 8150    // test multiple selection retrieves back correctly
 8151    cx.assert_editor_state(indoc!(
 8152        r#"abcd
 8153           ef«ghˇ»
 8154           «iˇ»jkl
 8155           «mˇ»nop"#
 8156    ));
 8157
 8158    cx.update_editor(|editor, window, cx| {
 8159        editor.add_selection_below(&Default::default(), window, cx);
 8160    });
 8161
 8162    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8163    cx.assert_editor_state(indoc!(
 8164        r#"abcd
 8165           ef«ghˇ»
 8166           ij«klˇ»
 8167           «mˇ»nop"#
 8168    ));
 8169
 8170    cx.update_editor(|editor, window, cx| {
 8171        editor.undo_selection(&Default::default(), window, cx);
 8172    });
 8173
 8174    // test undo
 8175    cx.assert_editor_state(indoc!(
 8176        r#"abcd
 8177           ef«ghˇ»
 8178           «iˇ»jkl
 8179           «mˇ»nop"#
 8180    ));
 8181
 8182    cx.update_editor(|editor, window, cx| {
 8183        editor.redo_selection(&Default::default(), window, cx);
 8184    });
 8185
 8186    // test redo
 8187    cx.assert_editor_state(indoc!(
 8188        r#"abcd
 8189           ef«ghˇ»
 8190           ij«klˇ»
 8191           «mˇ»nop"#
 8192    ));
 8193}
 8194
 8195#[gpui::test]
 8196async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8197    init_test(cx, |_| {});
 8198    let mut cx = EditorTestContext::new(cx).await;
 8199
 8200    cx.set_state(indoc!(
 8201        r#"line onˇe
 8202           liˇne two
 8203           line three
 8204           line four"#
 8205    ));
 8206
 8207    cx.update_editor(|editor, window, cx| {
 8208        editor.add_selection_below(&Default::default(), window, cx);
 8209        editor.add_selection_below(&Default::default(), window, cx);
 8210        editor.add_selection_below(&Default::default(), window, cx);
 8211    });
 8212
 8213    // initial state with two multi cursor groups
 8214    cx.assert_editor_state(indoc!(
 8215        r#"line onˇe
 8216           liˇne twˇo
 8217           liˇne thˇree
 8218           liˇne foˇur"#
 8219    ));
 8220
 8221    // add single cursor in middle - simulate opt click
 8222    cx.update_editor(|editor, window, cx| {
 8223        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8224        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8225        editor.end_selection(window, cx);
 8226    });
 8227
 8228    cx.assert_editor_state(indoc!(
 8229        r#"line onˇe
 8230           liˇne twˇo
 8231           liˇneˇ thˇree
 8232           liˇne foˇur"#
 8233    ));
 8234
 8235    cx.update_editor(|editor, window, cx| {
 8236        editor.add_selection_above(&Default::default(), window, cx);
 8237    });
 8238
 8239    // test new added selection expands above and existing selection shrinks
 8240    cx.assert_editor_state(indoc!(
 8241        r#"line onˇe
 8242           liˇneˇ twˇo
 8243           liˇneˇ thˇree
 8244           line four"#
 8245    ));
 8246
 8247    cx.update_editor(|editor, window, cx| {
 8248        editor.add_selection_above(&Default::default(), window, cx);
 8249    });
 8250
 8251    // test new added selection expands above and existing selection shrinks
 8252    cx.assert_editor_state(indoc!(
 8253        r#"lineˇ onˇe
 8254           liˇneˇ twˇo
 8255           lineˇ three
 8256           line four"#
 8257    ));
 8258
 8259    // intial state with two selection groups
 8260    cx.set_state(indoc!(
 8261        r#"abcd
 8262           ef«ghˇ»
 8263           ijkl
 8264           «mˇ»nop"#
 8265    ));
 8266
 8267    cx.update_editor(|editor, window, cx| {
 8268        editor.add_selection_above(&Default::default(), window, cx);
 8269        editor.add_selection_above(&Default::default(), window, cx);
 8270    });
 8271
 8272    cx.assert_editor_state(indoc!(
 8273        r#"ab«cdˇ»
 8274           «eˇ»f«ghˇ»
 8275           «iˇ»jkl
 8276           «mˇ»nop"#
 8277    ));
 8278
 8279    // add single selection in middle - simulate opt drag
 8280    cx.update_editor(|editor, window, cx| {
 8281        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8282        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8283        editor.update_selection(
 8284            DisplayPoint::new(DisplayRow(2), 4),
 8285            0,
 8286            gpui::Point::<f32>::default(),
 8287            window,
 8288            cx,
 8289        );
 8290        editor.end_selection(window, cx);
 8291    });
 8292
 8293    cx.assert_editor_state(indoc!(
 8294        r#"ab«cdˇ»
 8295           «eˇ»f«ghˇ»
 8296           «iˇ»jk«lˇ»
 8297           «mˇ»nop"#
 8298    ));
 8299
 8300    cx.update_editor(|editor, window, cx| {
 8301        editor.add_selection_below(&Default::default(), window, cx);
 8302    });
 8303
 8304    // test new added selection expands below, others shrinks from above
 8305    cx.assert_editor_state(indoc!(
 8306        r#"abcd
 8307           ef«ghˇ»
 8308           «iˇ»jk«lˇ»
 8309           «mˇ»no«pˇ»"#
 8310    ));
 8311}
 8312
 8313#[gpui::test]
 8314async fn test_select_next(cx: &mut TestAppContext) {
 8315    init_test(cx, |_| {});
 8316
 8317    let mut cx = EditorTestContext::new(cx).await;
 8318    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8319
 8320    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8321        .unwrap();
 8322    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8323
 8324    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8325        .unwrap();
 8326    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8327
 8328    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8329    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8330
 8331    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8332    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8333
 8334    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8335        .unwrap();
 8336    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8337
 8338    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8339        .unwrap();
 8340    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8341
 8342    // Test selection direction should be preserved
 8343    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8344
 8345    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8346        .unwrap();
 8347    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8348}
 8349
 8350#[gpui::test]
 8351async fn test_select_all_matches(cx: &mut TestAppContext) {
 8352    init_test(cx, |_| {});
 8353
 8354    let mut cx = EditorTestContext::new(cx).await;
 8355
 8356    // Test caret-only selections
 8357    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8358    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8359        .unwrap();
 8360    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8361
 8362    // Test left-to-right selections
 8363    cx.set_state("abc\n«abcˇ»\nabc");
 8364    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8365        .unwrap();
 8366    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8367
 8368    // Test right-to-left selections
 8369    cx.set_state("abc\n«ˇabc»\nabc");
 8370    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8371        .unwrap();
 8372    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8373
 8374    // Test selecting whitespace with caret selection
 8375    cx.set_state("abc\nˇ   abc\nabc");
 8376    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8377        .unwrap();
 8378    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8379
 8380    // Test selecting whitespace with left-to-right selection
 8381    cx.set_state("abc\n«ˇ  »abc\nabc");
 8382    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8383        .unwrap();
 8384    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8385
 8386    // Test no matches with right-to-left selection
 8387    cx.set_state("abc\n«  ˇ»abc\nabc");
 8388    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8389        .unwrap();
 8390    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8391
 8392    // Test with a single word and clip_at_line_ends=true (#29823)
 8393    cx.set_state("aˇbc");
 8394    cx.update_editor(|e, window, cx| {
 8395        e.set_clip_at_line_ends(true, cx);
 8396        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8397        e.set_clip_at_line_ends(false, cx);
 8398    });
 8399    cx.assert_editor_state("«abcˇ»");
 8400}
 8401
 8402#[gpui::test]
 8403async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8404    init_test(cx, |_| {});
 8405
 8406    let mut cx = EditorTestContext::new(cx).await;
 8407
 8408    let large_body_1 = "\nd".repeat(200);
 8409    let large_body_2 = "\ne".repeat(200);
 8410
 8411    cx.set_state(&format!(
 8412        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8413    ));
 8414    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8415        let scroll_position = editor.scroll_position(cx);
 8416        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8417        scroll_position
 8418    });
 8419
 8420    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8421        .unwrap();
 8422    cx.assert_editor_state(&format!(
 8423        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8424    ));
 8425    let scroll_position_after_selection =
 8426        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8427    assert_eq!(
 8428        initial_scroll_position, scroll_position_after_selection,
 8429        "Scroll position should not change after selecting all matches"
 8430    );
 8431}
 8432
 8433#[gpui::test]
 8434async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8435    init_test(cx, |_| {});
 8436
 8437    let mut cx = EditorLspTestContext::new_rust(
 8438        lsp::ServerCapabilities {
 8439            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8440            ..Default::default()
 8441        },
 8442        cx,
 8443    )
 8444    .await;
 8445
 8446    cx.set_state(indoc! {"
 8447        line 1
 8448        line 2
 8449        linˇe 3
 8450        line 4
 8451        line 5
 8452    "});
 8453
 8454    // Make an edit
 8455    cx.update_editor(|editor, window, cx| {
 8456        editor.handle_input("X", window, cx);
 8457    });
 8458
 8459    // Move cursor to a different position
 8460    cx.update_editor(|editor, window, cx| {
 8461        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8462            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8463        });
 8464    });
 8465
 8466    cx.assert_editor_state(indoc! {"
 8467        line 1
 8468        line 2
 8469        linXe 3
 8470        line 4
 8471        liˇne 5
 8472    "});
 8473
 8474    cx.lsp
 8475        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8476            Ok(Some(vec![lsp::TextEdit::new(
 8477                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8478                "PREFIX ".to_string(),
 8479            )]))
 8480        });
 8481
 8482    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8483        .unwrap()
 8484        .await
 8485        .unwrap();
 8486
 8487    cx.assert_editor_state(indoc! {"
 8488        PREFIX line 1
 8489        line 2
 8490        linXe 3
 8491        line 4
 8492        liˇne 5
 8493    "});
 8494
 8495    // Undo formatting
 8496    cx.update_editor(|editor, window, cx| {
 8497        editor.undo(&Default::default(), window, cx);
 8498    });
 8499
 8500    // Verify cursor moved back to position after edit
 8501    cx.assert_editor_state(indoc! {"
 8502        line 1
 8503        line 2
 8504        linXˇe 3
 8505        line 4
 8506        line 5
 8507    "});
 8508}
 8509
 8510#[gpui::test]
 8511async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8512    init_test(cx, |_| {});
 8513
 8514    let mut cx = EditorTestContext::new(cx).await;
 8515
 8516    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8517    cx.update_editor(|editor, window, cx| {
 8518        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8519    });
 8520
 8521    cx.set_state(indoc! {"
 8522        line 1
 8523        line 2
 8524        linˇe 3
 8525        line 4
 8526        line 5
 8527        line 6
 8528        line 7
 8529        line 8
 8530        line 9
 8531        line 10
 8532    "});
 8533
 8534    let snapshot = cx.buffer_snapshot();
 8535    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8536
 8537    cx.update(|_, cx| {
 8538        provider.update(cx, |provider, _| {
 8539            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8540                id: None,
 8541                edits: vec![(edit_position..edit_position, "X".into())],
 8542                edit_preview: None,
 8543            }))
 8544        })
 8545    });
 8546
 8547    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8548    cx.update_editor(|editor, window, cx| {
 8549        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8550    });
 8551
 8552    cx.assert_editor_state(indoc! {"
 8553        line 1
 8554        line 2
 8555        lineXˇ 3
 8556        line 4
 8557        line 5
 8558        line 6
 8559        line 7
 8560        line 8
 8561        line 9
 8562        line 10
 8563    "});
 8564
 8565    cx.update_editor(|editor, window, cx| {
 8566        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8567            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8568        });
 8569    });
 8570
 8571    cx.assert_editor_state(indoc! {"
 8572        line 1
 8573        line 2
 8574        lineX 3
 8575        line 4
 8576        line 5
 8577        line 6
 8578        line 7
 8579        line 8
 8580        line 9
 8581        liˇne 10
 8582    "});
 8583
 8584    cx.update_editor(|editor, window, cx| {
 8585        editor.undo(&Default::default(), window, cx);
 8586    });
 8587
 8588    cx.assert_editor_state(indoc! {"
 8589        line 1
 8590        line 2
 8591        lineˇ 3
 8592        line 4
 8593        line 5
 8594        line 6
 8595        line 7
 8596        line 8
 8597        line 9
 8598        line 10
 8599    "});
 8600}
 8601
 8602#[gpui::test]
 8603async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8604    init_test(cx, |_| {});
 8605
 8606    let mut cx = EditorTestContext::new(cx).await;
 8607    cx.set_state(
 8608        r#"let foo = 2;
 8609lˇet foo = 2;
 8610let fooˇ = 2;
 8611let foo = 2;
 8612let foo = ˇ2;"#,
 8613    );
 8614
 8615    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8616        .unwrap();
 8617    cx.assert_editor_state(
 8618        r#"let foo = 2;
 8619«letˇ» foo = 2;
 8620let «fooˇ» = 2;
 8621let foo = 2;
 8622let foo = «2ˇ»;"#,
 8623    );
 8624
 8625    // noop for multiple selections with different contents
 8626    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8627        .unwrap();
 8628    cx.assert_editor_state(
 8629        r#"let foo = 2;
 8630«letˇ» foo = 2;
 8631let «fooˇ» = 2;
 8632let foo = 2;
 8633let foo = «2ˇ»;"#,
 8634    );
 8635
 8636    // Test last selection direction should be preserved
 8637    cx.set_state(
 8638        r#"let foo = 2;
 8639let foo = 2;
 8640let «fooˇ» = 2;
 8641let «ˇfoo» = 2;
 8642let foo = 2;"#,
 8643    );
 8644
 8645    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8646        .unwrap();
 8647    cx.assert_editor_state(
 8648        r#"let foo = 2;
 8649let foo = 2;
 8650let «fooˇ» = 2;
 8651let «ˇfoo» = 2;
 8652let «ˇfoo» = 2;"#,
 8653    );
 8654}
 8655
 8656#[gpui::test]
 8657async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8658    init_test(cx, |_| {});
 8659
 8660    let mut cx =
 8661        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8662
 8663    cx.assert_editor_state(indoc! {"
 8664        ˇbbb
 8665        ccc
 8666
 8667        bbb
 8668        ccc
 8669        "});
 8670    cx.dispatch_action(SelectPrevious::default());
 8671    cx.assert_editor_state(indoc! {"
 8672                «bbbˇ»
 8673                ccc
 8674
 8675                bbb
 8676                ccc
 8677                "});
 8678    cx.dispatch_action(SelectPrevious::default());
 8679    cx.assert_editor_state(indoc! {"
 8680                «bbbˇ»
 8681                ccc
 8682
 8683                «bbbˇ»
 8684                ccc
 8685                "});
 8686}
 8687
 8688#[gpui::test]
 8689async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8690    init_test(cx, |_| {});
 8691
 8692    let mut cx = EditorTestContext::new(cx).await;
 8693    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8694
 8695    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8696        .unwrap();
 8697    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8698
 8699    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8700        .unwrap();
 8701    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8702
 8703    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8704    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8705
 8706    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8707    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8708
 8709    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8710        .unwrap();
 8711    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8712
 8713    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8714        .unwrap();
 8715    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8716}
 8717
 8718#[gpui::test]
 8719async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8720    init_test(cx, |_| {});
 8721
 8722    let mut cx = EditorTestContext::new(cx).await;
 8723    cx.set_state("");
 8724
 8725    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8726        .unwrap();
 8727    cx.assert_editor_state("«aˇ»");
 8728    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8729        .unwrap();
 8730    cx.assert_editor_state("«aˇ»");
 8731}
 8732
 8733#[gpui::test]
 8734async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8735    init_test(cx, |_| {});
 8736
 8737    let mut cx = EditorTestContext::new(cx).await;
 8738    cx.set_state(
 8739        r#"let foo = 2;
 8740lˇet foo = 2;
 8741let fooˇ = 2;
 8742let foo = 2;
 8743let foo = ˇ2;"#,
 8744    );
 8745
 8746    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8747        .unwrap();
 8748    cx.assert_editor_state(
 8749        r#"let foo = 2;
 8750«letˇ» foo = 2;
 8751let «fooˇ» = 2;
 8752let foo = 2;
 8753let foo = «2ˇ»;"#,
 8754    );
 8755
 8756    // noop for multiple selections with different contents
 8757    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8758        .unwrap();
 8759    cx.assert_editor_state(
 8760        r#"let foo = 2;
 8761«letˇ» foo = 2;
 8762let «fooˇ» = 2;
 8763let foo = 2;
 8764let foo = «2ˇ»;"#,
 8765    );
 8766}
 8767
 8768#[gpui::test]
 8769async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8770    init_test(cx, |_| {});
 8771
 8772    let mut cx = EditorTestContext::new(cx).await;
 8773    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8774
 8775    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8776        .unwrap();
 8777    // selection direction is preserved
 8778    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8779
 8780    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8781        .unwrap();
 8782    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8783
 8784    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8785    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8786
 8787    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8788    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8789
 8790    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8791        .unwrap();
 8792    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8793
 8794    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8795        .unwrap();
 8796    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8797}
 8798
 8799#[gpui::test]
 8800async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8801    init_test(cx, |_| {});
 8802
 8803    let language = Arc::new(Language::new(
 8804        LanguageConfig::default(),
 8805        Some(tree_sitter_rust::LANGUAGE.into()),
 8806    ));
 8807
 8808    let text = r#"
 8809        use mod1::mod2::{mod3, mod4};
 8810
 8811        fn fn_1(param1: bool, param2: &str) {
 8812            let var1 = "text";
 8813        }
 8814    "#
 8815    .unindent();
 8816
 8817    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8818    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8819    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8820
 8821    editor
 8822        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8823        .await;
 8824
 8825    editor.update_in(cx, |editor, window, cx| {
 8826        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8827            s.select_display_ranges([
 8828                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8829                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8830                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8831            ]);
 8832        });
 8833        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8834    });
 8835    editor.update(cx, |editor, cx| {
 8836        assert_text_with_selections(
 8837            editor,
 8838            indoc! {r#"
 8839                use mod1::mod2::{mod3, «mod4ˇ»};
 8840
 8841                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8842                    let var1 = "«ˇtext»";
 8843                }
 8844            "#},
 8845            cx,
 8846        );
 8847    });
 8848
 8849    editor.update_in(cx, |editor, window, cx| {
 8850        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8851    });
 8852    editor.update(cx, |editor, cx| {
 8853        assert_text_with_selections(
 8854            editor,
 8855            indoc! {r#"
 8856                use mod1::mod2::«{mod3, mod4}ˇ»;
 8857
 8858                «ˇfn fn_1(param1: bool, param2: &str) {
 8859                    let var1 = "text";
 8860 8861            "#},
 8862            cx,
 8863        );
 8864    });
 8865
 8866    editor.update_in(cx, |editor, window, cx| {
 8867        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8868    });
 8869    assert_eq!(
 8870        editor.update(cx, |editor, cx| editor
 8871            .selections
 8872            .display_ranges(&editor.display_snapshot(cx))),
 8873        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8874    );
 8875
 8876    // Trying to expand the selected syntax node one more time has no effect.
 8877    editor.update_in(cx, |editor, window, cx| {
 8878        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8879    });
 8880    assert_eq!(
 8881        editor.update(cx, |editor, cx| editor
 8882            .selections
 8883            .display_ranges(&editor.display_snapshot(cx))),
 8884        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8885    );
 8886
 8887    editor.update_in(cx, |editor, window, cx| {
 8888        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8889    });
 8890    editor.update(cx, |editor, cx| {
 8891        assert_text_with_selections(
 8892            editor,
 8893            indoc! {r#"
 8894                use mod1::mod2::«{mod3, mod4}ˇ»;
 8895
 8896                «ˇfn fn_1(param1: bool, param2: &str) {
 8897                    let var1 = "text";
 8898 8899            "#},
 8900            cx,
 8901        );
 8902    });
 8903
 8904    editor.update_in(cx, |editor, window, cx| {
 8905        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8906    });
 8907    editor.update(cx, |editor, cx| {
 8908        assert_text_with_selections(
 8909            editor,
 8910            indoc! {r#"
 8911                use mod1::mod2::{mod3, «mod4ˇ»};
 8912
 8913                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8914                    let var1 = "«ˇtext»";
 8915                }
 8916            "#},
 8917            cx,
 8918        );
 8919    });
 8920
 8921    editor.update_in(cx, |editor, window, cx| {
 8922        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8923    });
 8924    editor.update(cx, |editor, cx| {
 8925        assert_text_with_selections(
 8926            editor,
 8927            indoc! {r#"
 8928                use mod1::mod2::{mod3, moˇd4};
 8929
 8930                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8931                    let var1 = "teˇxt";
 8932                }
 8933            "#},
 8934            cx,
 8935        );
 8936    });
 8937
 8938    // Trying to shrink the selected syntax node one more time has no effect.
 8939    editor.update_in(cx, |editor, window, cx| {
 8940        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8941    });
 8942    editor.update_in(cx, |editor, _, cx| {
 8943        assert_text_with_selections(
 8944            editor,
 8945            indoc! {r#"
 8946                use mod1::mod2::{mod3, moˇd4};
 8947
 8948                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8949                    let var1 = "teˇxt";
 8950                }
 8951            "#},
 8952            cx,
 8953        );
 8954    });
 8955
 8956    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8957    // a fold.
 8958    editor.update_in(cx, |editor, window, cx| {
 8959        editor.fold_creases(
 8960            vec![
 8961                Crease::simple(
 8962                    Point::new(0, 21)..Point::new(0, 24),
 8963                    FoldPlaceholder::test(),
 8964                ),
 8965                Crease::simple(
 8966                    Point::new(3, 20)..Point::new(3, 22),
 8967                    FoldPlaceholder::test(),
 8968                ),
 8969            ],
 8970            true,
 8971            window,
 8972            cx,
 8973        );
 8974        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8975    });
 8976    editor.update(cx, |editor, cx| {
 8977        assert_text_with_selections(
 8978            editor,
 8979            indoc! {r#"
 8980                use mod1::mod2::«{mod3, mod4}ˇ»;
 8981
 8982                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8983                    let var1 = "«ˇtext»";
 8984                }
 8985            "#},
 8986            cx,
 8987        );
 8988    });
 8989}
 8990
 8991#[gpui::test]
 8992async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8993    init_test(cx, |_| {});
 8994
 8995    let language = Arc::new(Language::new(
 8996        LanguageConfig::default(),
 8997        Some(tree_sitter_rust::LANGUAGE.into()),
 8998    ));
 8999
 9000    let text = "let a = 2;";
 9001
 9002    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9003    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9004    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9005
 9006    editor
 9007        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9008        .await;
 9009
 9010    // Test case 1: Cursor at end of word
 9011    editor.update_in(cx, |editor, window, cx| {
 9012        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9013            s.select_display_ranges([
 9014                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9015            ]);
 9016        });
 9017    });
 9018    editor.update(cx, |editor, cx| {
 9019        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9020    });
 9021    editor.update_in(cx, |editor, window, cx| {
 9022        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9023    });
 9024    editor.update(cx, |editor, cx| {
 9025        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9026    });
 9027    editor.update_in(cx, |editor, window, cx| {
 9028        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9029    });
 9030    editor.update(cx, |editor, cx| {
 9031        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9032    });
 9033
 9034    // Test case 2: Cursor at end of statement
 9035    editor.update_in(cx, |editor, window, cx| {
 9036        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9037            s.select_display_ranges([
 9038                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9039            ]);
 9040        });
 9041    });
 9042    editor.update(cx, |editor, cx| {
 9043        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9044    });
 9045    editor.update_in(cx, |editor, window, cx| {
 9046        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9047    });
 9048    editor.update(cx, |editor, cx| {
 9049        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9050    });
 9051}
 9052
 9053#[gpui::test]
 9054async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9055    init_test(cx, |_| {});
 9056
 9057    let language = Arc::new(Language::new(
 9058        LanguageConfig {
 9059            name: "JavaScript".into(),
 9060            ..Default::default()
 9061        },
 9062        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9063    ));
 9064
 9065    let text = r#"
 9066        let a = {
 9067            key: "value",
 9068        };
 9069    "#
 9070    .unindent();
 9071
 9072    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9073    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9074    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9075
 9076    editor
 9077        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9078        .await;
 9079
 9080    // Test case 1: Cursor after '{'
 9081    editor.update_in(cx, |editor, window, cx| {
 9082        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9083            s.select_display_ranges([
 9084                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9085            ]);
 9086        });
 9087    });
 9088    editor.update(cx, |editor, cx| {
 9089        assert_text_with_selections(
 9090            editor,
 9091            indoc! {r#"
 9092                let a = {ˇ
 9093                    key: "value",
 9094                };
 9095            "#},
 9096            cx,
 9097        );
 9098    });
 9099    editor.update_in(cx, |editor, window, cx| {
 9100        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9101    });
 9102    editor.update(cx, |editor, cx| {
 9103        assert_text_with_selections(
 9104            editor,
 9105            indoc! {r#"
 9106                let a = «ˇ{
 9107                    key: "value",
 9108                }»;
 9109            "#},
 9110            cx,
 9111        );
 9112    });
 9113
 9114    // Test case 2: Cursor after ':'
 9115    editor.update_in(cx, |editor, window, cx| {
 9116        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9117            s.select_display_ranges([
 9118                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9119            ]);
 9120        });
 9121    });
 9122    editor.update(cx, |editor, cx| {
 9123        assert_text_with_selections(
 9124            editor,
 9125            indoc! {r#"
 9126                let a = {
 9127                    key:ˇ "value",
 9128                };
 9129            "#},
 9130            cx,
 9131        );
 9132    });
 9133    editor.update_in(cx, |editor, window, cx| {
 9134        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9135    });
 9136    editor.update(cx, |editor, cx| {
 9137        assert_text_with_selections(
 9138            editor,
 9139            indoc! {r#"
 9140                let a = {
 9141                    «ˇkey: "value"»,
 9142                };
 9143            "#},
 9144            cx,
 9145        );
 9146    });
 9147    editor.update_in(cx, |editor, window, cx| {
 9148        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9149    });
 9150    editor.update(cx, |editor, cx| {
 9151        assert_text_with_selections(
 9152            editor,
 9153            indoc! {r#"
 9154                let a = «ˇ{
 9155                    key: "value",
 9156                }»;
 9157            "#},
 9158            cx,
 9159        );
 9160    });
 9161
 9162    // Test case 3: Cursor after ','
 9163    editor.update_in(cx, |editor, window, cx| {
 9164        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9165            s.select_display_ranges([
 9166                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9167            ]);
 9168        });
 9169    });
 9170    editor.update(cx, |editor, cx| {
 9171        assert_text_with_selections(
 9172            editor,
 9173            indoc! {r#"
 9174                let a = {
 9175                    key: "value",ˇ
 9176                };
 9177            "#},
 9178            cx,
 9179        );
 9180    });
 9181    editor.update_in(cx, |editor, window, cx| {
 9182        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9183    });
 9184    editor.update(cx, |editor, cx| {
 9185        assert_text_with_selections(
 9186            editor,
 9187            indoc! {r#"
 9188                let a = «ˇ{
 9189                    key: "value",
 9190                }»;
 9191            "#},
 9192            cx,
 9193        );
 9194    });
 9195
 9196    // Test case 4: Cursor after ';'
 9197    editor.update_in(cx, |editor, window, cx| {
 9198        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9199            s.select_display_ranges([
 9200                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9201            ]);
 9202        });
 9203    });
 9204    editor.update(cx, |editor, cx| {
 9205        assert_text_with_selections(
 9206            editor,
 9207            indoc! {r#"
 9208                let a = {
 9209                    key: "value",
 9210                };ˇ
 9211            "#},
 9212            cx,
 9213        );
 9214    });
 9215    editor.update_in(cx, |editor, window, cx| {
 9216        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9217    });
 9218    editor.update(cx, |editor, cx| {
 9219        assert_text_with_selections(
 9220            editor,
 9221            indoc! {r#"
 9222                «ˇlet a = {
 9223                    key: "value",
 9224                };
 9225                »"#},
 9226            cx,
 9227        );
 9228    });
 9229}
 9230
 9231#[gpui::test]
 9232async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9233    init_test(cx, |_| {});
 9234
 9235    let language = Arc::new(Language::new(
 9236        LanguageConfig::default(),
 9237        Some(tree_sitter_rust::LANGUAGE.into()),
 9238    ));
 9239
 9240    let text = r#"
 9241        use mod1::mod2::{mod3, mod4};
 9242
 9243        fn fn_1(param1: bool, param2: &str) {
 9244            let var1 = "hello world";
 9245        }
 9246    "#
 9247    .unindent();
 9248
 9249    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9250    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9251    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9252
 9253    editor
 9254        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9255        .await;
 9256
 9257    // Test 1: Cursor on a letter of a string word
 9258    editor.update_in(cx, |editor, window, cx| {
 9259        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9260            s.select_display_ranges([
 9261                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9262            ]);
 9263        });
 9264    });
 9265    editor.update_in(cx, |editor, window, cx| {
 9266        assert_text_with_selections(
 9267            editor,
 9268            indoc! {r#"
 9269                use mod1::mod2::{mod3, mod4};
 9270
 9271                fn fn_1(param1: bool, param2: &str) {
 9272                    let var1 = "hˇello world";
 9273                }
 9274            "#},
 9275            cx,
 9276        );
 9277        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9278        assert_text_with_selections(
 9279            editor,
 9280            indoc! {r#"
 9281                use mod1::mod2::{mod3, mod4};
 9282
 9283                fn fn_1(param1: bool, param2: &str) {
 9284                    let var1 = "«ˇhello» world";
 9285                }
 9286            "#},
 9287            cx,
 9288        );
 9289    });
 9290
 9291    // Test 2: Partial selection within a word
 9292    editor.update_in(cx, |editor, window, cx| {
 9293        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9294            s.select_display_ranges([
 9295                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9296            ]);
 9297        });
 9298    });
 9299    editor.update_in(cx, |editor, window, cx| {
 9300        assert_text_with_selections(
 9301            editor,
 9302            indoc! {r#"
 9303                use mod1::mod2::{mod3, mod4};
 9304
 9305                fn fn_1(param1: bool, param2: &str) {
 9306                    let var1 = "h«elˇ»lo world";
 9307                }
 9308            "#},
 9309            cx,
 9310        );
 9311        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9312        assert_text_with_selections(
 9313            editor,
 9314            indoc! {r#"
 9315                use mod1::mod2::{mod3, mod4};
 9316
 9317                fn fn_1(param1: bool, param2: &str) {
 9318                    let var1 = "«ˇhello» world";
 9319                }
 9320            "#},
 9321            cx,
 9322        );
 9323    });
 9324
 9325    // Test 3: Complete word already selected
 9326    editor.update_in(cx, |editor, window, cx| {
 9327        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9328            s.select_display_ranges([
 9329                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9330            ]);
 9331        });
 9332    });
 9333    editor.update_in(cx, |editor, window, cx| {
 9334        assert_text_with_selections(
 9335            editor,
 9336            indoc! {r#"
 9337                use mod1::mod2::{mod3, mod4};
 9338
 9339                fn fn_1(param1: bool, param2: &str) {
 9340                    let var1 = "«helloˇ» world";
 9341                }
 9342            "#},
 9343            cx,
 9344        );
 9345        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9346        assert_text_with_selections(
 9347            editor,
 9348            indoc! {r#"
 9349                use mod1::mod2::{mod3, mod4};
 9350
 9351                fn fn_1(param1: bool, param2: &str) {
 9352                    let var1 = "«hello worldˇ»";
 9353                }
 9354            "#},
 9355            cx,
 9356        );
 9357    });
 9358
 9359    // Test 4: Selection spanning across words
 9360    editor.update_in(cx, |editor, window, cx| {
 9361        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9362            s.select_display_ranges([
 9363                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9364            ]);
 9365        });
 9366    });
 9367    editor.update_in(cx, |editor, window, cx| {
 9368        assert_text_with_selections(
 9369            editor,
 9370            indoc! {r#"
 9371                use mod1::mod2::{mod3, mod4};
 9372
 9373                fn fn_1(param1: bool, param2: &str) {
 9374                    let var1 = "hel«lo woˇ»rld";
 9375                }
 9376            "#},
 9377            cx,
 9378        );
 9379        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9380        assert_text_with_selections(
 9381            editor,
 9382            indoc! {r#"
 9383                use mod1::mod2::{mod3, mod4};
 9384
 9385                fn fn_1(param1: bool, param2: &str) {
 9386                    let var1 = "«ˇhello world»";
 9387                }
 9388            "#},
 9389            cx,
 9390        );
 9391    });
 9392
 9393    // Test 5: Expansion beyond string
 9394    editor.update_in(cx, |editor, window, cx| {
 9395        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9396        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9397        assert_text_with_selections(
 9398            editor,
 9399            indoc! {r#"
 9400                use mod1::mod2::{mod3, mod4};
 9401
 9402                fn fn_1(param1: bool, param2: &str) {
 9403                    «ˇlet var1 = "hello world";»
 9404                }
 9405            "#},
 9406            cx,
 9407        );
 9408    });
 9409}
 9410
 9411#[gpui::test]
 9412async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9413    init_test(cx, |_| {});
 9414
 9415    let mut cx = EditorTestContext::new(cx).await;
 9416
 9417    let language = Arc::new(Language::new(
 9418        LanguageConfig::default(),
 9419        Some(tree_sitter_rust::LANGUAGE.into()),
 9420    ));
 9421
 9422    cx.update_buffer(|buffer, cx| {
 9423        buffer.set_language(Some(language), cx);
 9424    });
 9425
 9426    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9427    cx.update_editor(|editor, window, cx| {
 9428        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9429    });
 9430
 9431    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9432
 9433    cx.set_state(indoc! { r#"fn a() {
 9434          // what
 9435          // a
 9436          // ˇlong
 9437          // method
 9438          // I
 9439          // sure
 9440          // hope
 9441          // it
 9442          // works
 9443    }"# });
 9444
 9445    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9446    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9447    cx.update(|_, cx| {
 9448        multi_buffer.update(cx, |multi_buffer, cx| {
 9449            multi_buffer.set_excerpts_for_path(
 9450                PathKey::for_buffer(&buffer, cx),
 9451                buffer,
 9452                [Point::new(1, 0)..Point::new(1, 0)],
 9453                3,
 9454                cx,
 9455            );
 9456        });
 9457    });
 9458
 9459    let editor2 = cx.new_window_entity(|window, cx| {
 9460        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9461    });
 9462
 9463    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9464    cx.update_editor(|editor, window, cx| {
 9465        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9466            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9467        })
 9468    });
 9469
 9470    cx.assert_editor_state(indoc! { "
 9471        fn a() {
 9472              // what
 9473              // a
 9474        ˇ      // long
 9475              // method"});
 9476
 9477    cx.update_editor(|editor, window, cx| {
 9478        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9479    });
 9480
 9481    // Although we could potentially make the action work when the syntax node
 9482    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9483    // did. Maybe we could also expand the excerpt to contain the range?
 9484    cx.assert_editor_state(indoc! { "
 9485        fn a() {
 9486              // what
 9487              // a
 9488        ˇ      // long
 9489              // method"});
 9490}
 9491
 9492#[gpui::test]
 9493async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9494    init_test(cx, |_| {});
 9495
 9496    let base_text = r#"
 9497        impl A {
 9498            // this is an uncommitted comment
 9499
 9500            fn b() {
 9501                c();
 9502            }
 9503
 9504            // this is another uncommitted comment
 9505
 9506            fn d() {
 9507                // e
 9508                // f
 9509            }
 9510        }
 9511
 9512        fn g() {
 9513            // h
 9514        }
 9515    "#
 9516    .unindent();
 9517
 9518    let text = r#"
 9519        ˇimpl A {
 9520
 9521            fn b() {
 9522                c();
 9523            }
 9524
 9525            fn d() {
 9526                // e
 9527                // f
 9528            }
 9529        }
 9530
 9531        fn g() {
 9532            // h
 9533        }
 9534    "#
 9535    .unindent();
 9536
 9537    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9538    cx.set_state(&text);
 9539    cx.set_head_text(&base_text);
 9540    cx.update_editor(|editor, window, cx| {
 9541        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9542    });
 9543
 9544    cx.assert_state_with_diff(
 9545        "
 9546        ˇimpl A {
 9547      -     // this is an uncommitted comment
 9548
 9549            fn b() {
 9550                c();
 9551            }
 9552
 9553      -     // this is another uncommitted comment
 9554      -
 9555            fn d() {
 9556                // e
 9557                // f
 9558            }
 9559        }
 9560
 9561        fn g() {
 9562            // h
 9563        }
 9564    "
 9565        .unindent(),
 9566    );
 9567
 9568    let expected_display_text = "
 9569        impl A {
 9570            // this is an uncommitted comment
 9571
 9572            fn b() {
 9573 9574            }
 9575
 9576            // this is another uncommitted comment
 9577
 9578            fn d() {
 9579 9580            }
 9581        }
 9582
 9583        fn g() {
 9584 9585        }
 9586        "
 9587    .unindent();
 9588
 9589    cx.update_editor(|editor, window, cx| {
 9590        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9591        assert_eq!(editor.display_text(cx), expected_display_text);
 9592    });
 9593}
 9594
 9595#[gpui::test]
 9596async fn test_autoindent(cx: &mut TestAppContext) {
 9597    init_test(cx, |_| {});
 9598
 9599    let language = Arc::new(
 9600        Language::new(
 9601            LanguageConfig {
 9602                brackets: BracketPairConfig {
 9603                    pairs: vec![
 9604                        BracketPair {
 9605                            start: "{".to_string(),
 9606                            end: "}".to_string(),
 9607                            close: false,
 9608                            surround: false,
 9609                            newline: true,
 9610                        },
 9611                        BracketPair {
 9612                            start: "(".to_string(),
 9613                            end: ")".to_string(),
 9614                            close: false,
 9615                            surround: false,
 9616                            newline: true,
 9617                        },
 9618                    ],
 9619                    ..Default::default()
 9620                },
 9621                ..Default::default()
 9622            },
 9623            Some(tree_sitter_rust::LANGUAGE.into()),
 9624        )
 9625        .with_indents_query(
 9626            r#"
 9627                (_ "(" ")" @end) @indent
 9628                (_ "{" "}" @end) @indent
 9629            "#,
 9630        )
 9631        .unwrap(),
 9632    );
 9633
 9634    let text = "fn a() {}";
 9635
 9636    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9637    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9638    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9639    editor
 9640        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9641        .await;
 9642
 9643    editor.update_in(cx, |editor, window, cx| {
 9644        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9645            s.select_ranges([5..5, 8..8, 9..9])
 9646        });
 9647        editor.newline(&Newline, window, cx);
 9648        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9649        assert_eq!(
 9650            editor.selections.ranges(&editor.display_snapshot(cx)),
 9651            &[
 9652                Point::new(1, 4)..Point::new(1, 4),
 9653                Point::new(3, 4)..Point::new(3, 4),
 9654                Point::new(5, 0)..Point::new(5, 0)
 9655            ]
 9656        );
 9657    });
 9658}
 9659
 9660#[gpui::test]
 9661async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9662    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9663
 9664    let language = Arc::new(
 9665        Language::new(
 9666            LanguageConfig {
 9667                brackets: BracketPairConfig {
 9668                    pairs: vec![
 9669                        BracketPair {
 9670                            start: "{".to_string(),
 9671                            end: "}".to_string(),
 9672                            close: false,
 9673                            surround: false,
 9674                            newline: true,
 9675                        },
 9676                        BracketPair {
 9677                            start: "(".to_string(),
 9678                            end: ")".to_string(),
 9679                            close: false,
 9680                            surround: false,
 9681                            newline: true,
 9682                        },
 9683                    ],
 9684                    ..Default::default()
 9685                },
 9686                ..Default::default()
 9687            },
 9688            Some(tree_sitter_rust::LANGUAGE.into()),
 9689        )
 9690        .with_indents_query(
 9691            r#"
 9692                (_ "(" ")" @end) @indent
 9693                (_ "{" "}" @end) @indent
 9694            "#,
 9695        )
 9696        .unwrap(),
 9697    );
 9698
 9699    let text = "fn a() {}";
 9700
 9701    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9702    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9703    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9704    editor
 9705        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9706        .await;
 9707
 9708    editor.update_in(cx, |editor, window, cx| {
 9709        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9710            s.select_ranges([5..5, 8..8, 9..9])
 9711        });
 9712        editor.newline(&Newline, window, cx);
 9713        assert_eq!(
 9714            editor.text(cx),
 9715            indoc!(
 9716                "
 9717                fn a(
 9718
 9719                ) {
 9720
 9721                }
 9722                "
 9723            )
 9724        );
 9725        assert_eq!(
 9726            editor.selections.ranges(&editor.display_snapshot(cx)),
 9727            &[
 9728                Point::new(1, 0)..Point::new(1, 0),
 9729                Point::new(3, 0)..Point::new(3, 0),
 9730                Point::new(5, 0)..Point::new(5, 0)
 9731            ]
 9732        );
 9733    });
 9734}
 9735
 9736#[gpui::test]
 9737async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9738    init_test(cx, |settings| {
 9739        settings.defaults.auto_indent = Some(true);
 9740        settings.languages.0.insert(
 9741            "python".into(),
 9742            LanguageSettingsContent {
 9743                auto_indent: Some(false),
 9744                ..Default::default()
 9745            },
 9746        );
 9747    });
 9748
 9749    let mut cx = EditorTestContext::new(cx).await;
 9750
 9751    let injected_language = Arc::new(
 9752        Language::new(
 9753            LanguageConfig {
 9754                brackets: BracketPairConfig {
 9755                    pairs: vec![
 9756                        BracketPair {
 9757                            start: "{".to_string(),
 9758                            end: "}".to_string(),
 9759                            close: false,
 9760                            surround: false,
 9761                            newline: true,
 9762                        },
 9763                        BracketPair {
 9764                            start: "(".to_string(),
 9765                            end: ")".to_string(),
 9766                            close: true,
 9767                            surround: false,
 9768                            newline: true,
 9769                        },
 9770                    ],
 9771                    ..Default::default()
 9772                },
 9773                name: "python".into(),
 9774                ..Default::default()
 9775            },
 9776            Some(tree_sitter_python::LANGUAGE.into()),
 9777        )
 9778        .with_indents_query(
 9779            r#"
 9780                (_ "(" ")" @end) @indent
 9781                (_ "{" "}" @end) @indent
 9782            "#,
 9783        )
 9784        .unwrap(),
 9785    );
 9786
 9787    let language = Arc::new(
 9788        Language::new(
 9789            LanguageConfig {
 9790                brackets: BracketPairConfig {
 9791                    pairs: vec![
 9792                        BracketPair {
 9793                            start: "{".to_string(),
 9794                            end: "}".to_string(),
 9795                            close: false,
 9796                            surround: false,
 9797                            newline: true,
 9798                        },
 9799                        BracketPair {
 9800                            start: "(".to_string(),
 9801                            end: ")".to_string(),
 9802                            close: true,
 9803                            surround: false,
 9804                            newline: true,
 9805                        },
 9806                    ],
 9807                    ..Default::default()
 9808                },
 9809                name: LanguageName::new("rust"),
 9810                ..Default::default()
 9811            },
 9812            Some(tree_sitter_rust::LANGUAGE.into()),
 9813        )
 9814        .with_indents_query(
 9815            r#"
 9816                (_ "(" ")" @end) @indent
 9817                (_ "{" "}" @end) @indent
 9818            "#,
 9819        )
 9820        .unwrap()
 9821        .with_injection_query(
 9822            r#"
 9823            (macro_invocation
 9824                macro: (identifier) @_macro_name
 9825                (token_tree) @injection.content
 9826                (#set! injection.language "python"))
 9827           "#,
 9828        )
 9829        .unwrap(),
 9830    );
 9831
 9832    cx.language_registry().add(injected_language);
 9833    cx.language_registry().add(language.clone());
 9834
 9835    cx.update_buffer(|buffer, cx| {
 9836        buffer.set_language(Some(language), cx);
 9837    });
 9838
 9839    cx.set_state(r#"struct A {ˇ}"#);
 9840
 9841    cx.update_editor(|editor, window, cx| {
 9842        editor.newline(&Default::default(), window, cx);
 9843    });
 9844
 9845    cx.assert_editor_state(indoc!(
 9846        "struct A {
 9847            ˇ
 9848        }"
 9849    ));
 9850
 9851    cx.set_state(r#"select_biased!(ˇ)"#);
 9852
 9853    cx.update_editor(|editor, window, cx| {
 9854        editor.newline(&Default::default(), window, cx);
 9855        editor.handle_input("def ", window, cx);
 9856        editor.handle_input("(", window, cx);
 9857        editor.newline(&Default::default(), window, cx);
 9858        editor.handle_input("a", window, cx);
 9859    });
 9860
 9861    cx.assert_editor_state(indoc!(
 9862        "select_biased!(
 9863        def (
 9864 9865        )
 9866        )"
 9867    ));
 9868}
 9869
 9870#[gpui::test]
 9871async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9872    init_test(cx, |_| {});
 9873
 9874    {
 9875        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9876        cx.set_state(indoc! {"
 9877            impl A {
 9878
 9879                fn b() {}
 9880
 9881            «fn c() {
 9882
 9883            }ˇ»
 9884            }
 9885        "});
 9886
 9887        cx.update_editor(|editor, window, cx| {
 9888            editor.autoindent(&Default::default(), window, cx);
 9889        });
 9890
 9891        cx.assert_editor_state(indoc! {"
 9892            impl A {
 9893
 9894                fn b() {}
 9895
 9896                «fn c() {
 9897
 9898                }ˇ»
 9899            }
 9900        "});
 9901    }
 9902
 9903    {
 9904        let mut cx = EditorTestContext::new_multibuffer(
 9905            cx,
 9906            [indoc! { "
 9907                impl A {
 9908                «
 9909                // a
 9910                fn b(){}
 9911                »
 9912                «
 9913                    }
 9914                    fn c(){}
 9915                »
 9916            "}],
 9917        );
 9918
 9919        let buffer = cx.update_editor(|editor, _, cx| {
 9920            let buffer = editor.buffer().update(cx, |buffer, _| {
 9921                buffer.all_buffers().iter().next().unwrap().clone()
 9922            });
 9923            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9924            buffer
 9925        });
 9926
 9927        cx.run_until_parked();
 9928        cx.update_editor(|editor, window, cx| {
 9929            editor.select_all(&Default::default(), window, cx);
 9930            editor.autoindent(&Default::default(), window, cx)
 9931        });
 9932        cx.run_until_parked();
 9933
 9934        cx.update(|_, cx| {
 9935            assert_eq!(
 9936                buffer.read(cx).text(),
 9937                indoc! { "
 9938                    impl A {
 9939
 9940                        // a
 9941                        fn b(){}
 9942
 9943
 9944                    }
 9945                    fn c(){}
 9946
 9947                " }
 9948            )
 9949        });
 9950    }
 9951}
 9952
 9953#[gpui::test]
 9954async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9955    init_test(cx, |_| {});
 9956
 9957    let mut cx = EditorTestContext::new(cx).await;
 9958
 9959    let language = Arc::new(Language::new(
 9960        LanguageConfig {
 9961            brackets: BracketPairConfig {
 9962                pairs: vec![
 9963                    BracketPair {
 9964                        start: "{".to_string(),
 9965                        end: "}".to_string(),
 9966                        close: true,
 9967                        surround: true,
 9968                        newline: true,
 9969                    },
 9970                    BracketPair {
 9971                        start: "(".to_string(),
 9972                        end: ")".to_string(),
 9973                        close: true,
 9974                        surround: true,
 9975                        newline: true,
 9976                    },
 9977                    BracketPair {
 9978                        start: "/*".to_string(),
 9979                        end: " */".to_string(),
 9980                        close: true,
 9981                        surround: true,
 9982                        newline: true,
 9983                    },
 9984                    BracketPair {
 9985                        start: "[".to_string(),
 9986                        end: "]".to_string(),
 9987                        close: false,
 9988                        surround: false,
 9989                        newline: true,
 9990                    },
 9991                    BracketPair {
 9992                        start: "\"".to_string(),
 9993                        end: "\"".to_string(),
 9994                        close: true,
 9995                        surround: true,
 9996                        newline: false,
 9997                    },
 9998                    BracketPair {
 9999                        start: "<".to_string(),
10000                        end: ">".to_string(),
10001                        close: false,
10002                        surround: true,
10003                        newline: true,
10004                    },
10005                ],
10006                ..Default::default()
10007            },
10008            autoclose_before: "})]".to_string(),
10009            ..Default::default()
10010        },
10011        Some(tree_sitter_rust::LANGUAGE.into()),
10012    ));
10013
10014    cx.language_registry().add(language.clone());
10015    cx.update_buffer(|buffer, cx| {
10016        buffer.set_language(Some(language), cx);
10017    });
10018
10019    cx.set_state(
10020        &r#"
10021            🏀ˇ
10022            εˇ
10023            ❤️ˇ
10024        "#
10025        .unindent(),
10026    );
10027
10028    // autoclose multiple nested brackets at multiple cursors
10029    cx.update_editor(|editor, window, cx| {
10030        editor.handle_input("{", window, cx);
10031        editor.handle_input("{", window, cx);
10032        editor.handle_input("{", window, cx);
10033    });
10034    cx.assert_editor_state(
10035        &"
10036            🏀{{{ˇ}}}
10037            ε{{{ˇ}}}
10038            ❤️{{{ˇ}}}
10039        "
10040        .unindent(),
10041    );
10042
10043    // insert a different closing bracket
10044    cx.update_editor(|editor, window, cx| {
10045        editor.handle_input(")", window, cx);
10046    });
10047    cx.assert_editor_state(
10048        &"
10049            🏀{{{)ˇ}}}
10050            ε{{{)ˇ}}}
10051            ❤️{{{)ˇ}}}
10052        "
10053        .unindent(),
10054    );
10055
10056    // skip over the auto-closed brackets when typing a closing bracket
10057    cx.update_editor(|editor, window, cx| {
10058        editor.move_right(&MoveRight, window, cx);
10059        editor.handle_input("}", window, cx);
10060        editor.handle_input("}", window, cx);
10061        editor.handle_input("}", window, cx);
10062    });
10063    cx.assert_editor_state(
10064        &"
10065            🏀{{{)}}}}ˇ
10066            ε{{{)}}}}ˇ
10067            ❤️{{{)}}}}ˇ
10068        "
10069        .unindent(),
10070    );
10071
10072    // autoclose multi-character pairs
10073    cx.set_state(
10074        &"
10075            ˇ
10076            ˇ
10077        "
10078        .unindent(),
10079    );
10080    cx.update_editor(|editor, window, cx| {
10081        editor.handle_input("/", window, cx);
10082        editor.handle_input("*", window, cx);
10083    });
10084    cx.assert_editor_state(
10085        &"
10086            /*ˇ */
10087            /*ˇ */
10088        "
10089        .unindent(),
10090    );
10091
10092    // one cursor autocloses a multi-character pair, one cursor
10093    // does not autoclose.
10094    cx.set_state(
10095        &"
1009610097            ˇ
10098        "
10099        .unindent(),
10100    );
10101    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10102    cx.assert_editor_state(
10103        &"
10104            /*ˇ */
1010510106        "
10107        .unindent(),
10108    );
10109
10110    // Don't autoclose if the next character isn't whitespace and isn't
10111    // listed in the language's "autoclose_before" section.
10112    cx.set_state("ˇa b");
10113    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10114    cx.assert_editor_state("{ˇa b");
10115
10116    // Don't autoclose if `close` is false for the bracket pair
10117    cx.set_state("ˇ");
10118    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10119    cx.assert_editor_state("");
10120
10121    // Surround with brackets if text is selected
10122    cx.set_state("«aˇ» b");
10123    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10124    cx.assert_editor_state("{«aˇ»} b");
10125
10126    // Autoclose when not immediately after a word character
10127    cx.set_state("a ˇ");
10128    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10129    cx.assert_editor_state("a \"ˇ\"");
10130
10131    // Autoclose pair where the start and end characters are the same
10132    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10133    cx.assert_editor_state("a \"\"ˇ");
10134
10135    // Don't autoclose when immediately after a word character
10136    cx.set_state("");
10137    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10138    cx.assert_editor_state("a\"ˇ");
10139
10140    // Do autoclose when after a non-word character
10141    cx.set_state("");
10142    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10143    cx.assert_editor_state("{\"ˇ\"");
10144
10145    // Non identical pairs autoclose regardless of preceding character
10146    cx.set_state("");
10147    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10148    cx.assert_editor_state("a{ˇ}");
10149
10150    // Don't autoclose pair if autoclose is disabled
10151    cx.set_state("ˇ");
10152    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10153    cx.assert_editor_state("");
10154
10155    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10156    cx.set_state("«aˇ» b");
10157    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10158    cx.assert_editor_state("<«aˇ»> b");
10159}
10160
10161#[gpui::test]
10162async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10163    init_test(cx, |settings| {
10164        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10165    });
10166
10167    let mut cx = EditorTestContext::new(cx).await;
10168
10169    let language = Arc::new(Language::new(
10170        LanguageConfig {
10171            brackets: BracketPairConfig {
10172                pairs: vec![
10173                    BracketPair {
10174                        start: "{".to_string(),
10175                        end: "}".to_string(),
10176                        close: true,
10177                        surround: true,
10178                        newline: true,
10179                    },
10180                    BracketPair {
10181                        start: "(".to_string(),
10182                        end: ")".to_string(),
10183                        close: true,
10184                        surround: true,
10185                        newline: true,
10186                    },
10187                    BracketPair {
10188                        start: "[".to_string(),
10189                        end: "]".to_string(),
10190                        close: false,
10191                        surround: false,
10192                        newline: true,
10193                    },
10194                ],
10195                ..Default::default()
10196            },
10197            autoclose_before: "})]".to_string(),
10198            ..Default::default()
10199        },
10200        Some(tree_sitter_rust::LANGUAGE.into()),
10201    ));
10202
10203    cx.language_registry().add(language.clone());
10204    cx.update_buffer(|buffer, cx| {
10205        buffer.set_language(Some(language), cx);
10206    });
10207
10208    cx.set_state(
10209        &"
10210            ˇ
10211            ˇ
10212            ˇ
10213        "
10214        .unindent(),
10215    );
10216
10217    // ensure only matching closing brackets are skipped over
10218    cx.update_editor(|editor, window, cx| {
10219        editor.handle_input("}", window, cx);
10220        editor.move_left(&MoveLeft, window, cx);
10221        editor.handle_input(")", window, cx);
10222        editor.move_left(&MoveLeft, window, cx);
10223    });
10224    cx.assert_editor_state(
10225        &"
10226            ˇ)}
10227            ˇ)}
10228            ˇ)}
10229        "
10230        .unindent(),
10231    );
10232
10233    // skip-over closing brackets at multiple cursors
10234    cx.update_editor(|editor, window, cx| {
10235        editor.handle_input(")", window, cx);
10236        editor.handle_input("}", window, cx);
10237    });
10238    cx.assert_editor_state(
10239        &"
10240            )}ˇ
10241            )}ˇ
10242            )}ˇ
10243        "
10244        .unindent(),
10245    );
10246
10247    // ignore non-close brackets
10248    cx.update_editor(|editor, window, cx| {
10249        editor.handle_input("]", window, cx);
10250        editor.move_left(&MoveLeft, window, cx);
10251        editor.handle_input("]", window, cx);
10252    });
10253    cx.assert_editor_state(
10254        &"
10255            )}]ˇ]
10256            )}]ˇ]
10257            )}]ˇ]
10258        "
10259        .unindent(),
10260    );
10261}
10262
10263#[gpui::test]
10264async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10265    init_test(cx, |_| {});
10266
10267    let mut cx = EditorTestContext::new(cx).await;
10268
10269    let html_language = Arc::new(
10270        Language::new(
10271            LanguageConfig {
10272                name: "HTML".into(),
10273                brackets: BracketPairConfig {
10274                    pairs: vec![
10275                        BracketPair {
10276                            start: "<".into(),
10277                            end: ">".into(),
10278                            close: true,
10279                            ..Default::default()
10280                        },
10281                        BracketPair {
10282                            start: "{".into(),
10283                            end: "}".into(),
10284                            close: true,
10285                            ..Default::default()
10286                        },
10287                        BracketPair {
10288                            start: "(".into(),
10289                            end: ")".into(),
10290                            close: true,
10291                            ..Default::default()
10292                        },
10293                    ],
10294                    ..Default::default()
10295                },
10296                autoclose_before: "})]>".into(),
10297                ..Default::default()
10298            },
10299            Some(tree_sitter_html::LANGUAGE.into()),
10300        )
10301        .with_injection_query(
10302            r#"
10303            (script_element
10304                (raw_text) @injection.content
10305                (#set! injection.language "javascript"))
10306            "#,
10307        )
10308        .unwrap(),
10309    );
10310
10311    let javascript_language = Arc::new(Language::new(
10312        LanguageConfig {
10313            name: "JavaScript".into(),
10314            brackets: BracketPairConfig {
10315                pairs: vec![
10316                    BracketPair {
10317                        start: "/*".into(),
10318                        end: " */".into(),
10319                        close: true,
10320                        ..Default::default()
10321                    },
10322                    BracketPair {
10323                        start: "{".into(),
10324                        end: "}".into(),
10325                        close: true,
10326                        ..Default::default()
10327                    },
10328                    BracketPair {
10329                        start: "(".into(),
10330                        end: ")".into(),
10331                        close: true,
10332                        ..Default::default()
10333                    },
10334                ],
10335                ..Default::default()
10336            },
10337            autoclose_before: "})]>".into(),
10338            ..Default::default()
10339        },
10340        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10341    ));
10342
10343    cx.language_registry().add(html_language.clone());
10344    cx.language_registry().add(javascript_language);
10345    cx.executor().run_until_parked();
10346
10347    cx.update_buffer(|buffer, cx| {
10348        buffer.set_language(Some(html_language), cx);
10349    });
10350
10351    cx.set_state(
10352        &r#"
10353            <body>ˇ
10354                <script>
10355                    var x = 1;ˇ
10356                </script>
10357            </body>ˇ
10358        "#
10359        .unindent(),
10360    );
10361
10362    // Precondition: different languages are active at different locations.
10363    cx.update_editor(|editor, window, cx| {
10364        let snapshot = editor.snapshot(window, cx);
10365        let cursors = editor
10366            .selections
10367            .ranges::<usize>(&editor.display_snapshot(cx));
10368        let languages = cursors
10369            .iter()
10370            .map(|c| snapshot.language_at(c.start).unwrap().name())
10371            .collect::<Vec<_>>();
10372        assert_eq!(
10373            languages,
10374            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10375        );
10376    });
10377
10378    // Angle brackets autoclose in HTML, but not JavaScript.
10379    cx.update_editor(|editor, window, cx| {
10380        editor.handle_input("<", window, cx);
10381        editor.handle_input("a", window, cx);
10382    });
10383    cx.assert_editor_state(
10384        &r#"
10385            <body><aˇ>
10386                <script>
10387                    var x = 1;<aˇ
10388                </script>
10389            </body><aˇ>
10390        "#
10391        .unindent(),
10392    );
10393
10394    // Curly braces and parens autoclose in both HTML and JavaScript.
10395    cx.update_editor(|editor, window, cx| {
10396        editor.handle_input(" b=", window, cx);
10397        editor.handle_input("{", window, cx);
10398        editor.handle_input("c", window, cx);
10399        editor.handle_input("(", window, cx);
10400    });
10401    cx.assert_editor_state(
10402        &r#"
10403            <body><a b={c(ˇ)}>
10404                <script>
10405                    var x = 1;<a b={c(ˇ)}
10406                </script>
10407            </body><a b={c(ˇ)}>
10408        "#
10409        .unindent(),
10410    );
10411
10412    // Brackets that were already autoclosed are skipped.
10413    cx.update_editor(|editor, window, cx| {
10414        editor.handle_input(")", window, cx);
10415        editor.handle_input("d", window, cx);
10416        editor.handle_input("}", window, cx);
10417    });
10418    cx.assert_editor_state(
10419        &r#"
10420            <body><a b={c()d}ˇ>
10421                <script>
10422                    var x = 1;<a b={c()d}ˇ
10423                </script>
10424            </body><a b={c()d}ˇ>
10425        "#
10426        .unindent(),
10427    );
10428    cx.update_editor(|editor, window, cx| {
10429        editor.handle_input(">", window, cx);
10430    });
10431    cx.assert_editor_state(
10432        &r#"
10433            <body><a b={c()d}>ˇ
10434                <script>
10435                    var x = 1;<a b={c()d}>ˇ
10436                </script>
10437            </body><a b={c()d}>ˇ
10438        "#
10439        .unindent(),
10440    );
10441
10442    // Reset
10443    cx.set_state(
10444        &r#"
10445            <body>ˇ
10446                <script>
10447                    var x = 1;ˇ
10448                </script>
10449            </body>ˇ
10450        "#
10451        .unindent(),
10452    );
10453
10454    cx.update_editor(|editor, window, cx| {
10455        editor.handle_input("<", window, cx);
10456    });
10457    cx.assert_editor_state(
10458        &r#"
10459            <body><ˇ>
10460                <script>
10461                    var x = 1;<ˇ
10462                </script>
10463            </body><ˇ>
10464        "#
10465        .unindent(),
10466    );
10467
10468    // When backspacing, the closing angle brackets are removed.
10469    cx.update_editor(|editor, window, cx| {
10470        editor.backspace(&Backspace, window, cx);
10471    });
10472    cx.assert_editor_state(
10473        &r#"
10474            <body>ˇ
10475                <script>
10476                    var x = 1;ˇ
10477                </script>
10478            </body>ˇ
10479        "#
10480        .unindent(),
10481    );
10482
10483    // Block comments autoclose in JavaScript, but not HTML.
10484    cx.update_editor(|editor, window, cx| {
10485        editor.handle_input("/", window, cx);
10486        editor.handle_input("*", window, cx);
10487    });
10488    cx.assert_editor_state(
10489        &r#"
10490            <body>/*ˇ
10491                <script>
10492                    var x = 1;/*ˇ */
10493                </script>
10494            </body>/*ˇ
10495        "#
10496        .unindent(),
10497    );
10498}
10499
10500#[gpui::test]
10501async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10502    init_test(cx, |_| {});
10503
10504    let mut cx = EditorTestContext::new(cx).await;
10505
10506    let rust_language = Arc::new(
10507        Language::new(
10508            LanguageConfig {
10509                name: "Rust".into(),
10510                brackets: serde_json::from_value(json!([
10511                    { "start": "{", "end": "}", "close": true, "newline": true },
10512                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10513                ]))
10514                .unwrap(),
10515                autoclose_before: "})]>".into(),
10516                ..Default::default()
10517            },
10518            Some(tree_sitter_rust::LANGUAGE.into()),
10519        )
10520        .with_override_query("(string_literal) @string")
10521        .unwrap(),
10522    );
10523
10524    cx.language_registry().add(rust_language.clone());
10525    cx.update_buffer(|buffer, cx| {
10526        buffer.set_language(Some(rust_language), cx);
10527    });
10528
10529    cx.set_state(
10530        &r#"
10531            let x = ˇ
10532        "#
10533        .unindent(),
10534    );
10535
10536    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10537    cx.update_editor(|editor, window, cx| {
10538        editor.handle_input("\"", window, cx);
10539    });
10540    cx.assert_editor_state(
10541        &r#"
10542            let x = "ˇ"
10543        "#
10544        .unindent(),
10545    );
10546
10547    // Inserting another quotation mark. The cursor moves across the existing
10548    // automatically-inserted quotation mark.
10549    cx.update_editor(|editor, window, cx| {
10550        editor.handle_input("\"", window, cx);
10551    });
10552    cx.assert_editor_state(
10553        &r#"
10554            let x = ""ˇ
10555        "#
10556        .unindent(),
10557    );
10558
10559    // Reset
10560    cx.set_state(
10561        &r#"
10562            let x = ˇ
10563        "#
10564        .unindent(),
10565    );
10566
10567    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10568    cx.update_editor(|editor, window, cx| {
10569        editor.handle_input("\"", window, cx);
10570        editor.handle_input(" ", window, cx);
10571        editor.move_left(&Default::default(), window, cx);
10572        editor.handle_input("\\", window, cx);
10573        editor.handle_input("\"", window, cx);
10574    });
10575    cx.assert_editor_state(
10576        &r#"
10577            let x = "\"ˇ "
10578        "#
10579        .unindent(),
10580    );
10581
10582    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10583    // mark. Nothing is inserted.
10584    cx.update_editor(|editor, window, cx| {
10585        editor.move_right(&Default::default(), window, cx);
10586        editor.handle_input("\"", window, cx);
10587    });
10588    cx.assert_editor_state(
10589        &r#"
10590            let x = "\" "ˇ
10591        "#
10592        .unindent(),
10593    );
10594}
10595
10596#[gpui::test]
10597async fn test_surround_with_pair(cx: &mut TestAppContext) {
10598    init_test(cx, |_| {});
10599
10600    let language = Arc::new(Language::new(
10601        LanguageConfig {
10602            brackets: BracketPairConfig {
10603                pairs: vec![
10604                    BracketPair {
10605                        start: "{".to_string(),
10606                        end: "}".to_string(),
10607                        close: true,
10608                        surround: true,
10609                        newline: true,
10610                    },
10611                    BracketPair {
10612                        start: "/* ".to_string(),
10613                        end: "*/".to_string(),
10614                        close: true,
10615                        surround: true,
10616                        ..Default::default()
10617                    },
10618                ],
10619                ..Default::default()
10620            },
10621            ..Default::default()
10622        },
10623        Some(tree_sitter_rust::LANGUAGE.into()),
10624    ));
10625
10626    let text = r#"
10627        a
10628        b
10629        c
10630    "#
10631    .unindent();
10632
10633    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10634    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10635    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10636    editor
10637        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10638        .await;
10639
10640    editor.update_in(cx, |editor, window, cx| {
10641        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10642            s.select_display_ranges([
10643                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10644                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10645                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10646            ])
10647        });
10648
10649        editor.handle_input("{", window, cx);
10650        editor.handle_input("{", window, cx);
10651        editor.handle_input("{", window, cx);
10652        assert_eq!(
10653            editor.text(cx),
10654            "
10655                {{{a}}}
10656                {{{b}}}
10657                {{{c}}}
10658            "
10659            .unindent()
10660        );
10661        assert_eq!(
10662            display_ranges(editor, cx),
10663            [
10664                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10665                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10666                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10667            ]
10668        );
10669
10670        editor.undo(&Undo, window, cx);
10671        editor.undo(&Undo, window, cx);
10672        editor.undo(&Undo, window, cx);
10673        assert_eq!(
10674            editor.text(cx),
10675            "
10676                a
10677                b
10678                c
10679            "
10680            .unindent()
10681        );
10682        assert_eq!(
10683            display_ranges(editor, cx),
10684            [
10685                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10686                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10687                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10688            ]
10689        );
10690
10691        // Ensure inserting the first character of a multi-byte bracket pair
10692        // doesn't surround the selections with the bracket.
10693        editor.handle_input("/", window, cx);
10694        assert_eq!(
10695            editor.text(cx),
10696            "
10697                /
10698                /
10699                /
10700            "
10701            .unindent()
10702        );
10703        assert_eq!(
10704            display_ranges(editor, cx),
10705            [
10706                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10707                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10708                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10709            ]
10710        );
10711
10712        editor.undo(&Undo, window, cx);
10713        assert_eq!(
10714            editor.text(cx),
10715            "
10716                a
10717                b
10718                c
10719            "
10720            .unindent()
10721        );
10722        assert_eq!(
10723            display_ranges(editor, cx),
10724            [
10725                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10726                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10727                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10728            ]
10729        );
10730
10731        // Ensure inserting the last character of a multi-byte bracket pair
10732        // doesn't surround the selections with the bracket.
10733        editor.handle_input("*", window, cx);
10734        assert_eq!(
10735            editor.text(cx),
10736            "
10737                *
10738                *
10739                *
10740            "
10741            .unindent()
10742        );
10743        assert_eq!(
10744            display_ranges(editor, cx),
10745            [
10746                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10747                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10748                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10749            ]
10750        );
10751    });
10752}
10753
10754#[gpui::test]
10755async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10756    init_test(cx, |_| {});
10757
10758    let language = Arc::new(Language::new(
10759        LanguageConfig {
10760            brackets: BracketPairConfig {
10761                pairs: vec![BracketPair {
10762                    start: "{".to_string(),
10763                    end: "}".to_string(),
10764                    close: true,
10765                    surround: true,
10766                    newline: true,
10767                }],
10768                ..Default::default()
10769            },
10770            autoclose_before: "}".to_string(),
10771            ..Default::default()
10772        },
10773        Some(tree_sitter_rust::LANGUAGE.into()),
10774    ));
10775
10776    let text = r#"
10777        a
10778        b
10779        c
10780    "#
10781    .unindent();
10782
10783    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10784    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10785    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10786    editor
10787        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10788        .await;
10789
10790    editor.update_in(cx, |editor, window, cx| {
10791        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10792            s.select_ranges([
10793                Point::new(0, 1)..Point::new(0, 1),
10794                Point::new(1, 1)..Point::new(1, 1),
10795                Point::new(2, 1)..Point::new(2, 1),
10796            ])
10797        });
10798
10799        editor.handle_input("{", window, cx);
10800        editor.handle_input("{", window, cx);
10801        editor.handle_input("_", window, cx);
10802        assert_eq!(
10803            editor.text(cx),
10804            "
10805                a{{_}}
10806                b{{_}}
10807                c{{_}}
10808            "
10809            .unindent()
10810        );
10811        assert_eq!(
10812            editor
10813                .selections
10814                .ranges::<Point>(&editor.display_snapshot(cx)),
10815            [
10816                Point::new(0, 4)..Point::new(0, 4),
10817                Point::new(1, 4)..Point::new(1, 4),
10818                Point::new(2, 4)..Point::new(2, 4)
10819            ]
10820        );
10821
10822        editor.backspace(&Default::default(), window, cx);
10823        editor.backspace(&Default::default(), window, cx);
10824        assert_eq!(
10825            editor.text(cx),
10826            "
10827                a{}
10828                b{}
10829                c{}
10830            "
10831            .unindent()
10832        );
10833        assert_eq!(
10834            editor
10835                .selections
10836                .ranges::<Point>(&editor.display_snapshot(cx)),
10837            [
10838                Point::new(0, 2)..Point::new(0, 2),
10839                Point::new(1, 2)..Point::new(1, 2),
10840                Point::new(2, 2)..Point::new(2, 2)
10841            ]
10842        );
10843
10844        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10845        assert_eq!(
10846            editor.text(cx),
10847            "
10848                a
10849                b
10850                c
10851            "
10852            .unindent()
10853        );
10854        assert_eq!(
10855            editor
10856                .selections
10857                .ranges::<Point>(&editor.display_snapshot(cx)),
10858            [
10859                Point::new(0, 1)..Point::new(0, 1),
10860                Point::new(1, 1)..Point::new(1, 1),
10861                Point::new(2, 1)..Point::new(2, 1)
10862            ]
10863        );
10864    });
10865}
10866
10867#[gpui::test]
10868async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10869    init_test(cx, |settings| {
10870        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10871    });
10872
10873    let mut cx = EditorTestContext::new(cx).await;
10874
10875    let language = Arc::new(Language::new(
10876        LanguageConfig {
10877            brackets: BracketPairConfig {
10878                pairs: vec![
10879                    BracketPair {
10880                        start: "{".to_string(),
10881                        end: "}".to_string(),
10882                        close: true,
10883                        surround: true,
10884                        newline: true,
10885                    },
10886                    BracketPair {
10887                        start: "(".to_string(),
10888                        end: ")".to_string(),
10889                        close: true,
10890                        surround: true,
10891                        newline: true,
10892                    },
10893                    BracketPair {
10894                        start: "[".to_string(),
10895                        end: "]".to_string(),
10896                        close: false,
10897                        surround: true,
10898                        newline: true,
10899                    },
10900                ],
10901                ..Default::default()
10902            },
10903            autoclose_before: "})]".to_string(),
10904            ..Default::default()
10905        },
10906        Some(tree_sitter_rust::LANGUAGE.into()),
10907    ));
10908
10909    cx.language_registry().add(language.clone());
10910    cx.update_buffer(|buffer, cx| {
10911        buffer.set_language(Some(language), cx);
10912    });
10913
10914    cx.set_state(
10915        &"
10916            {(ˇ)}
10917            [[ˇ]]
10918            {(ˇ)}
10919        "
10920        .unindent(),
10921    );
10922
10923    cx.update_editor(|editor, window, cx| {
10924        editor.backspace(&Default::default(), window, cx);
10925        editor.backspace(&Default::default(), window, cx);
10926    });
10927
10928    cx.assert_editor_state(
10929        &"
10930            ˇ
10931            ˇ]]
10932            ˇ
10933        "
10934        .unindent(),
10935    );
10936
10937    cx.update_editor(|editor, window, cx| {
10938        editor.handle_input("{", window, cx);
10939        editor.handle_input("{", window, cx);
10940        editor.move_right(&MoveRight, window, cx);
10941        editor.move_right(&MoveRight, window, cx);
10942        editor.move_left(&MoveLeft, window, cx);
10943        editor.move_left(&MoveLeft, window, cx);
10944        editor.backspace(&Default::default(), window, cx);
10945    });
10946
10947    cx.assert_editor_state(
10948        &"
10949            {ˇ}
10950            {ˇ}]]
10951            {ˇ}
10952        "
10953        .unindent(),
10954    );
10955
10956    cx.update_editor(|editor, window, cx| {
10957        editor.backspace(&Default::default(), window, cx);
10958    });
10959
10960    cx.assert_editor_state(
10961        &"
10962            ˇ
10963            ˇ]]
10964            ˇ
10965        "
10966        .unindent(),
10967    );
10968}
10969
10970#[gpui::test]
10971async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10972    init_test(cx, |_| {});
10973
10974    let language = Arc::new(Language::new(
10975        LanguageConfig::default(),
10976        Some(tree_sitter_rust::LANGUAGE.into()),
10977    ));
10978
10979    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10980    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10981    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10982    editor
10983        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10984        .await;
10985
10986    editor.update_in(cx, |editor, window, cx| {
10987        editor.set_auto_replace_emoji_shortcode(true);
10988
10989        editor.handle_input("Hello ", window, cx);
10990        editor.handle_input(":wave", window, cx);
10991        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10992
10993        editor.handle_input(":", window, cx);
10994        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10995
10996        editor.handle_input(" :smile", window, cx);
10997        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10998
10999        editor.handle_input(":", window, cx);
11000        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11001
11002        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11003        editor.handle_input(":wave", window, cx);
11004        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11005
11006        editor.handle_input(":", window, cx);
11007        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11008
11009        editor.handle_input(":1", window, cx);
11010        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11011
11012        editor.handle_input(":", window, cx);
11013        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11014
11015        // Ensure shortcode does not get replaced when it is part of a word
11016        editor.handle_input(" Test:wave", window, cx);
11017        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11018
11019        editor.handle_input(":", window, cx);
11020        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11021
11022        editor.set_auto_replace_emoji_shortcode(false);
11023
11024        // Ensure shortcode does not get replaced when auto replace is off
11025        editor.handle_input(" :wave", window, cx);
11026        assert_eq!(
11027            editor.text(cx),
11028            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11029        );
11030
11031        editor.handle_input(":", window, cx);
11032        assert_eq!(
11033            editor.text(cx),
11034            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11035        );
11036    });
11037}
11038
11039#[gpui::test]
11040async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11041    init_test(cx, |_| {});
11042
11043    let (text, insertion_ranges) = marked_text_ranges(
11044        indoc! {"
11045            ˇ
11046        "},
11047        false,
11048    );
11049
11050    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11051    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11052
11053    _ = editor.update_in(cx, |editor, window, cx| {
11054        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11055
11056        editor
11057            .insert_snippet(&insertion_ranges, snippet, window, cx)
11058            .unwrap();
11059
11060        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11061            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11062            assert_eq!(editor.text(cx), expected_text);
11063            assert_eq!(
11064                editor
11065                    .selections
11066                    .ranges::<usize>(&editor.display_snapshot(cx)),
11067                selection_ranges
11068            );
11069        }
11070
11071        assert(
11072            editor,
11073            cx,
11074            indoc! {"
11075            type «» =•
11076            "},
11077        );
11078
11079        assert!(editor.context_menu_visible(), "There should be a matches");
11080    });
11081}
11082
11083#[gpui::test]
11084async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11085    init_test(cx, |_| {});
11086
11087    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11088        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11089        assert_eq!(editor.text(cx), expected_text);
11090        assert_eq!(
11091            editor
11092                .selections
11093                .ranges::<usize>(&editor.display_snapshot(cx)),
11094            selection_ranges
11095        );
11096    }
11097
11098    let (text, insertion_ranges) = marked_text_ranges(
11099        indoc! {"
11100            ˇ
11101        "},
11102        false,
11103    );
11104
11105    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11106    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11107
11108    _ = editor.update_in(cx, |editor, window, cx| {
11109        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11110
11111        editor
11112            .insert_snippet(&insertion_ranges, snippet, window, cx)
11113            .unwrap();
11114
11115        assert_state(
11116            editor,
11117            cx,
11118            indoc! {"
11119            type «» = ;•
11120            "},
11121        );
11122
11123        assert!(
11124            editor.context_menu_visible(),
11125            "Context menu should be visible for placeholder choices"
11126        );
11127
11128        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11129
11130        assert_state(
11131            editor,
11132            cx,
11133            indoc! {"
11134            type  = «»;•
11135            "},
11136        );
11137
11138        assert!(
11139            !editor.context_menu_visible(),
11140            "Context menu should be hidden after moving to next tabstop"
11141        );
11142
11143        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11144
11145        assert_state(
11146            editor,
11147            cx,
11148            indoc! {"
11149            type  = ; ˇ
11150            "},
11151        );
11152
11153        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11154
11155        assert_state(
11156            editor,
11157            cx,
11158            indoc! {"
11159            type  = ; ˇ
11160            "},
11161        );
11162    });
11163
11164    _ = editor.update_in(cx, |editor, window, cx| {
11165        editor.select_all(&SelectAll, window, cx);
11166        editor.backspace(&Backspace, window, cx);
11167
11168        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11169        let insertion_ranges = editor
11170            .selections
11171            .all(&editor.display_snapshot(cx))
11172            .iter()
11173            .map(|s| s.range())
11174            .collect::<Vec<_>>();
11175
11176        editor
11177            .insert_snippet(&insertion_ranges, snippet, window, cx)
11178            .unwrap();
11179
11180        assert_state(editor, cx, "fn «» = value;•");
11181
11182        assert!(
11183            editor.context_menu_visible(),
11184            "Context menu should be visible for placeholder choices"
11185        );
11186
11187        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11188
11189        assert_state(editor, cx, "fn  = «valueˇ»;•");
11190
11191        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11192
11193        assert_state(editor, cx, "fn «» = value;•");
11194
11195        assert!(
11196            editor.context_menu_visible(),
11197            "Context menu should be visible again after returning to first tabstop"
11198        );
11199
11200        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11201
11202        assert_state(editor, cx, "fn «» = value;•");
11203    });
11204}
11205
11206#[gpui::test]
11207async fn test_snippets(cx: &mut TestAppContext) {
11208    init_test(cx, |_| {});
11209
11210    let mut cx = EditorTestContext::new(cx).await;
11211
11212    cx.set_state(indoc! {"
11213        a.ˇ b
11214        a.ˇ b
11215        a.ˇ b
11216    "});
11217
11218    cx.update_editor(|editor, window, cx| {
11219        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11220        let insertion_ranges = editor
11221            .selections
11222            .all(&editor.display_snapshot(cx))
11223            .iter()
11224            .map(|s| s.range())
11225            .collect::<Vec<_>>();
11226        editor
11227            .insert_snippet(&insertion_ranges, snippet, window, cx)
11228            .unwrap();
11229    });
11230
11231    cx.assert_editor_state(indoc! {"
11232        a.f(«oneˇ», two, «threeˇ») b
11233        a.f(«oneˇ», two, «threeˇ») b
11234        a.f(«oneˇ», two, «threeˇ») b
11235    "});
11236
11237    // Can't move earlier than the first tab stop
11238    cx.update_editor(|editor, window, cx| {
11239        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11240    });
11241    cx.assert_editor_state(indoc! {"
11242        a.f(«oneˇ», two, «threeˇ») b
11243        a.f(«oneˇ», two, «threeˇ») b
11244        a.f(«oneˇ», two, «threeˇ») b
11245    "});
11246
11247    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11248    cx.assert_editor_state(indoc! {"
11249        a.f(one, «twoˇ», three) b
11250        a.f(one, «twoˇ», three) b
11251        a.f(one, «twoˇ», three) b
11252    "});
11253
11254    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11255    cx.assert_editor_state(indoc! {"
11256        a.f(«oneˇ», two, «threeˇ») b
11257        a.f(«oneˇ», two, «threeˇ») b
11258        a.f(«oneˇ», two, «threeˇ») b
11259    "});
11260
11261    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11262    cx.assert_editor_state(indoc! {"
11263        a.f(one, «twoˇ», three) b
11264        a.f(one, «twoˇ», three) b
11265        a.f(one, «twoˇ», three) b
11266    "});
11267    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11268    cx.assert_editor_state(indoc! {"
11269        a.f(one, two, three)ˇ b
11270        a.f(one, two, three)ˇ b
11271        a.f(one, two, three)ˇ b
11272    "});
11273
11274    // As soon as the last tab stop is reached, snippet state is gone
11275    cx.update_editor(|editor, window, cx| {
11276        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11277    });
11278    cx.assert_editor_state(indoc! {"
11279        a.f(one, two, three)ˇ b
11280        a.f(one, two, three)ˇ b
11281        a.f(one, two, three)ˇ b
11282    "});
11283}
11284
11285#[gpui::test]
11286async fn test_snippet_indentation(cx: &mut TestAppContext) {
11287    init_test(cx, |_| {});
11288
11289    let mut cx = EditorTestContext::new(cx).await;
11290
11291    cx.update_editor(|editor, window, cx| {
11292        let snippet = Snippet::parse(indoc! {"
11293            /*
11294             * Multiline comment with leading indentation
11295             *
11296             * $1
11297             */
11298            $0"})
11299        .unwrap();
11300        let insertion_ranges = editor
11301            .selections
11302            .all(&editor.display_snapshot(cx))
11303            .iter()
11304            .map(|s| s.range())
11305            .collect::<Vec<_>>();
11306        editor
11307            .insert_snippet(&insertion_ranges, snippet, window, cx)
11308            .unwrap();
11309    });
11310
11311    cx.assert_editor_state(indoc! {"
11312        /*
11313         * Multiline comment with leading indentation
11314         *
11315         * ˇ
11316         */
11317    "});
11318
11319    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11320    cx.assert_editor_state(indoc! {"
11321        /*
11322         * Multiline comment with leading indentation
11323         *
11324         *•
11325         */
11326        ˇ"});
11327}
11328
11329#[gpui::test]
11330async fn test_document_format_during_save(cx: &mut TestAppContext) {
11331    init_test(cx, |_| {});
11332
11333    let fs = FakeFs::new(cx.executor());
11334    fs.insert_file(path!("/file.rs"), Default::default()).await;
11335
11336    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11337
11338    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11339    language_registry.add(rust_lang());
11340    let mut fake_servers = language_registry.register_fake_lsp(
11341        "Rust",
11342        FakeLspAdapter {
11343            capabilities: lsp::ServerCapabilities {
11344                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11345                ..Default::default()
11346            },
11347            ..Default::default()
11348        },
11349    );
11350
11351    let buffer = project
11352        .update(cx, |project, cx| {
11353            project.open_local_buffer(path!("/file.rs"), cx)
11354        })
11355        .await
11356        .unwrap();
11357
11358    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11359    let (editor, cx) = cx.add_window_view(|window, cx| {
11360        build_editor_with_project(project.clone(), buffer, window, cx)
11361    });
11362    editor.update_in(cx, |editor, window, cx| {
11363        editor.set_text("one\ntwo\nthree\n", window, cx)
11364    });
11365    assert!(cx.read(|cx| editor.is_dirty(cx)));
11366
11367    cx.executor().start_waiting();
11368    let fake_server = fake_servers.next().await.unwrap();
11369
11370    {
11371        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11372            move |params, _| async move {
11373                assert_eq!(
11374                    params.text_document.uri,
11375                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11376                );
11377                assert_eq!(params.options.tab_size, 4);
11378                Ok(Some(vec![lsp::TextEdit::new(
11379                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11380                    ", ".to_string(),
11381                )]))
11382            },
11383        );
11384        let save = editor
11385            .update_in(cx, |editor, window, cx| {
11386                editor.save(
11387                    SaveOptions {
11388                        format: true,
11389                        autosave: false,
11390                    },
11391                    project.clone(),
11392                    window,
11393                    cx,
11394                )
11395            })
11396            .unwrap();
11397        cx.executor().start_waiting();
11398        save.await;
11399
11400        assert_eq!(
11401            editor.update(cx, |editor, cx| editor.text(cx)),
11402            "one, two\nthree\n"
11403        );
11404        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11405    }
11406
11407    {
11408        editor.update_in(cx, |editor, window, cx| {
11409            editor.set_text("one\ntwo\nthree\n", window, cx)
11410        });
11411        assert!(cx.read(|cx| editor.is_dirty(cx)));
11412
11413        // Ensure we can still save even if formatting hangs.
11414        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11415            move |params, _| async move {
11416                assert_eq!(
11417                    params.text_document.uri,
11418                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11419                );
11420                futures::future::pending::<()>().await;
11421                unreachable!()
11422            },
11423        );
11424        let save = editor
11425            .update_in(cx, |editor, window, cx| {
11426                editor.save(
11427                    SaveOptions {
11428                        format: true,
11429                        autosave: false,
11430                    },
11431                    project.clone(),
11432                    window,
11433                    cx,
11434                )
11435            })
11436            .unwrap();
11437        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11438        cx.executor().start_waiting();
11439        save.await;
11440        assert_eq!(
11441            editor.update(cx, |editor, cx| editor.text(cx)),
11442            "one\ntwo\nthree\n"
11443        );
11444    }
11445
11446    // Set rust language override and assert overridden tabsize is sent to language server
11447    update_test_language_settings(cx, |settings| {
11448        settings.languages.0.insert(
11449            "Rust".into(),
11450            LanguageSettingsContent {
11451                tab_size: NonZeroU32::new(8),
11452                ..Default::default()
11453            },
11454        );
11455    });
11456
11457    {
11458        editor.update_in(cx, |editor, window, cx| {
11459            editor.set_text("somehting_new\n", window, cx)
11460        });
11461        assert!(cx.read(|cx| editor.is_dirty(cx)));
11462        let _formatting_request_signal = fake_server
11463            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11464                assert_eq!(
11465                    params.text_document.uri,
11466                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11467                );
11468                assert_eq!(params.options.tab_size, 8);
11469                Ok(Some(vec![]))
11470            });
11471        let save = editor
11472            .update_in(cx, |editor, window, cx| {
11473                editor.save(
11474                    SaveOptions {
11475                        format: true,
11476                        autosave: false,
11477                    },
11478                    project.clone(),
11479                    window,
11480                    cx,
11481                )
11482            })
11483            .unwrap();
11484        cx.executor().start_waiting();
11485        save.await;
11486    }
11487}
11488
11489#[gpui::test]
11490async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11491    init_test(cx, |settings| {
11492        settings.defaults.ensure_final_newline_on_save = Some(false);
11493    });
11494
11495    let fs = FakeFs::new(cx.executor());
11496    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11497
11498    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11499
11500    let buffer = project
11501        .update(cx, |project, cx| {
11502            project.open_local_buffer(path!("/file.txt"), cx)
11503        })
11504        .await
11505        .unwrap();
11506
11507    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11508    let (editor, cx) = cx.add_window_view(|window, cx| {
11509        build_editor_with_project(project.clone(), buffer, window, cx)
11510    });
11511    editor.update_in(cx, |editor, window, cx| {
11512        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11513            s.select_ranges([0..0])
11514        });
11515    });
11516    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11517
11518    editor.update_in(cx, |editor, window, cx| {
11519        editor.handle_input("\n", window, cx)
11520    });
11521    cx.run_until_parked();
11522    save(&editor, &project, cx).await;
11523    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11524
11525    editor.update_in(cx, |editor, window, cx| {
11526        editor.undo(&Default::default(), window, cx);
11527    });
11528    save(&editor, &project, cx).await;
11529    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11530
11531    editor.update_in(cx, |editor, window, cx| {
11532        editor.redo(&Default::default(), window, cx);
11533    });
11534    cx.run_until_parked();
11535    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11536
11537    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11538        let save = editor
11539            .update_in(cx, |editor, window, cx| {
11540                editor.save(
11541                    SaveOptions {
11542                        format: true,
11543                        autosave: false,
11544                    },
11545                    project.clone(),
11546                    window,
11547                    cx,
11548                )
11549            })
11550            .unwrap();
11551        cx.executor().start_waiting();
11552        save.await;
11553        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11554    }
11555}
11556
11557#[gpui::test]
11558async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11559    init_test(cx, |_| {});
11560
11561    let cols = 4;
11562    let rows = 10;
11563    let sample_text_1 = sample_text(rows, cols, 'a');
11564    assert_eq!(
11565        sample_text_1,
11566        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11567    );
11568    let sample_text_2 = sample_text(rows, cols, 'l');
11569    assert_eq!(
11570        sample_text_2,
11571        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11572    );
11573    let sample_text_3 = sample_text(rows, cols, 'v');
11574    assert_eq!(
11575        sample_text_3,
11576        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11577    );
11578
11579    let fs = FakeFs::new(cx.executor());
11580    fs.insert_tree(
11581        path!("/a"),
11582        json!({
11583            "main.rs": sample_text_1,
11584            "other.rs": sample_text_2,
11585            "lib.rs": sample_text_3,
11586        }),
11587    )
11588    .await;
11589
11590    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11591    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11592    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11593
11594    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11595    language_registry.add(rust_lang());
11596    let mut fake_servers = language_registry.register_fake_lsp(
11597        "Rust",
11598        FakeLspAdapter {
11599            capabilities: lsp::ServerCapabilities {
11600                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11601                ..Default::default()
11602            },
11603            ..Default::default()
11604        },
11605    );
11606
11607    let worktree = project.update(cx, |project, cx| {
11608        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11609        assert_eq!(worktrees.len(), 1);
11610        worktrees.pop().unwrap()
11611    });
11612    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11613
11614    let buffer_1 = project
11615        .update(cx, |project, cx| {
11616            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11617        })
11618        .await
11619        .unwrap();
11620    let buffer_2 = project
11621        .update(cx, |project, cx| {
11622            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11623        })
11624        .await
11625        .unwrap();
11626    let buffer_3 = project
11627        .update(cx, |project, cx| {
11628            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11629        })
11630        .await
11631        .unwrap();
11632
11633    let multi_buffer = cx.new(|cx| {
11634        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11635        multi_buffer.push_excerpts(
11636            buffer_1.clone(),
11637            [
11638                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11639                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11640                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11641            ],
11642            cx,
11643        );
11644        multi_buffer.push_excerpts(
11645            buffer_2.clone(),
11646            [
11647                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11648                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11649                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11650            ],
11651            cx,
11652        );
11653        multi_buffer.push_excerpts(
11654            buffer_3.clone(),
11655            [
11656                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11657                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11658                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11659            ],
11660            cx,
11661        );
11662        multi_buffer
11663    });
11664    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11665        Editor::new(
11666            EditorMode::full(),
11667            multi_buffer,
11668            Some(project.clone()),
11669            window,
11670            cx,
11671        )
11672    });
11673
11674    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11675        editor.change_selections(
11676            SelectionEffects::scroll(Autoscroll::Next),
11677            window,
11678            cx,
11679            |s| s.select_ranges(Some(1..2)),
11680        );
11681        editor.insert("|one|two|three|", window, cx);
11682    });
11683    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11684    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11685        editor.change_selections(
11686            SelectionEffects::scroll(Autoscroll::Next),
11687            window,
11688            cx,
11689            |s| s.select_ranges(Some(60..70)),
11690        );
11691        editor.insert("|four|five|six|", window, cx);
11692    });
11693    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11694
11695    // First two buffers should be edited, but not the third one.
11696    assert_eq!(
11697        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11698        "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}",
11699    );
11700    buffer_1.update(cx, |buffer, _| {
11701        assert!(buffer.is_dirty());
11702        assert_eq!(
11703            buffer.text(),
11704            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11705        )
11706    });
11707    buffer_2.update(cx, |buffer, _| {
11708        assert!(buffer.is_dirty());
11709        assert_eq!(
11710            buffer.text(),
11711            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11712        )
11713    });
11714    buffer_3.update(cx, |buffer, _| {
11715        assert!(!buffer.is_dirty());
11716        assert_eq!(buffer.text(), sample_text_3,)
11717    });
11718    cx.executor().run_until_parked();
11719
11720    cx.executor().start_waiting();
11721    let save = multi_buffer_editor
11722        .update_in(cx, |editor, window, cx| {
11723            editor.save(
11724                SaveOptions {
11725                    format: true,
11726                    autosave: false,
11727                },
11728                project.clone(),
11729                window,
11730                cx,
11731            )
11732        })
11733        .unwrap();
11734
11735    let fake_server = fake_servers.next().await.unwrap();
11736    fake_server
11737        .server
11738        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11739            Ok(Some(vec![lsp::TextEdit::new(
11740                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11741                format!("[{} formatted]", params.text_document.uri),
11742            )]))
11743        })
11744        .detach();
11745    save.await;
11746
11747    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11748    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11749    assert_eq!(
11750        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11751        uri!(
11752            "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}"
11753        ),
11754    );
11755    buffer_1.update(cx, |buffer, _| {
11756        assert!(!buffer.is_dirty());
11757        assert_eq!(
11758            buffer.text(),
11759            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11760        )
11761    });
11762    buffer_2.update(cx, |buffer, _| {
11763        assert!(!buffer.is_dirty());
11764        assert_eq!(
11765            buffer.text(),
11766            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11767        )
11768    });
11769    buffer_3.update(cx, |buffer, _| {
11770        assert!(!buffer.is_dirty());
11771        assert_eq!(buffer.text(), sample_text_3,)
11772    });
11773}
11774
11775#[gpui::test]
11776async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11777    init_test(cx, |_| {});
11778
11779    let fs = FakeFs::new(cx.executor());
11780    fs.insert_tree(
11781        path!("/dir"),
11782        json!({
11783            "file1.rs": "fn main() { println!(\"hello\"); }",
11784            "file2.rs": "fn test() { println!(\"test\"); }",
11785            "file3.rs": "fn other() { println!(\"other\"); }\n",
11786        }),
11787    )
11788    .await;
11789
11790    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11791    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11792    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11793
11794    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11795    language_registry.add(rust_lang());
11796
11797    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11798    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11799
11800    // Open three buffers
11801    let buffer_1 = project
11802        .update(cx, |project, cx| {
11803            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11804        })
11805        .await
11806        .unwrap();
11807    let buffer_2 = project
11808        .update(cx, |project, cx| {
11809            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11810        })
11811        .await
11812        .unwrap();
11813    let buffer_3 = project
11814        .update(cx, |project, cx| {
11815            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11816        })
11817        .await
11818        .unwrap();
11819
11820    // Create a multi-buffer with all three buffers
11821    let multi_buffer = cx.new(|cx| {
11822        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11823        multi_buffer.push_excerpts(
11824            buffer_1.clone(),
11825            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11826            cx,
11827        );
11828        multi_buffer.push_excerpts(
11829            buffer_2.clone(),
11830            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11831            cx,
11832        );
11833        multi_buffer.push_excerpts(
11834            buffer_3.clone(),
11835            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11836            cx,
11837        );
11838        multi_buffer
11839    });
11840
11841    let editor = cx.new_window_entity(|window, cx| {
11842        Editor::new(
11843            EditorMode::full(),
11844            multi_buffer,
11845            Some(project.clone()),
11846            window,
11847            cx,
11848        )
11849    });
11850
11851    // Edit only the first buffer
11852    editor.update_in(cx, |editor, window, cx| {
11853        editor.change_selections(
11854            SelectionEffects::scroll(Autoscroll::Next),
11855            window,
11856            cx,
11857            |s| s.select_ranges(Some(10..10)),
11858        );
11859        editor.insert("// edited", window, cx);
11860    });
11861
11862    // Verify that only buffer 1 is dirty
11863    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11864    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11865    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11866
11867    // Get write counts after file creation (files were created with initial content)
11868    // We expect each file to have been written once during creation
11869    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11870    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11871    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11872
11873    // Perform autosave
11874    let save_task = editor.update_in(cx, |editor, window, cx| {
11875        editor.save(
11876            SaveOptions {
11877                format: true,
11878                autosave: true,
11879            },
11880            project.clone(),
11881            window,
11882            cx,
11883        )
11884    });
11885    save_task.await.unwrap();
11886
11887    // Only the dirty buffer should have been saved
11888    assert_eq!(
11889        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11890        1,
11891        "Buffer 1 was dirty, so it should have been written once during autosave"
11892    );
11893    assert_eq!(
11894        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11895        0,
11896        "Buffer 2 was clean, so it should not have been written during autosave"
11897    );
11898    assert_eq!(
11899        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11900        0,
11901        "Buffer 3 was clean, so it should not have been written during autosave"
11902    );
11903
11904    // Verify buffer states after autosave
11905    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11906    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11907    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11908
11909    // Now perform a manual save (format = true)
11910    let save_task = editor.update_in(cx, |editor, window, cx| {
11911        editor.save(
11912            SaveOptions {
11913                format: true,
11914                autosave: false,
11915            },
11916            project.clone(),
11917            window,
11918            cx,
11919        )
11920    });
11921    save_task.await.unwrap();
11922
11923    // During manual save, clean buffers don't get written to disk
11924    // They just get did_save called for language server notifications
11925    assert_eq!(
11926        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11927        1,
11928        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11929    );
11930    assert_eq!(
11931        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11932        0,
11933        "Buffer 2 should not have been written at all"
11934    );
11935    assert_eq!(
11936        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11937        0,
11938        "Buffer 3 should not have been written at all"
11939    );
11940}
11941
11942async fn setup_range_format_test(
11943    cx: &mut TestAppContext,
11944) -> (
11945    Entity<Project>,
11946    Entity<Editor>,
11947    &mut gpui::VisualTestContext,
11948    lsp::FakeLanguageServer,
11949) {
11950    init_test(cx, |_| {});
11951
11952    let fs = FakeFs::new(cx.executor());
11953    fs.insert_file(path!("/file.rs"), Default::default()).await;
11954
11955    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11956
11957    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11958    language_registry.add(rust_lang());
11959    let mut fake_servers = language_registry.register_fake_lsp(
11960        "Rust",
11961        FakeLspAdapter {
11962            capabilities: lsp::ServerCapabilities {
11963                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11964                ..lsp::ServerCapabilities::default()
11965            },
11966            ..FakeLspAdapter::default()
11967        },
11968    );
11969
11970    let buffer = project
11971        .update(cx, |project, cx| {
11972            project.open_local_buffer(path!("/file.rs"), cx)
11973        })
11974        .await
11975        .unwrap();
11976
11977    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11978    let (editor, cx) = cx.add_window_view(|window, cx| {
11979        build_editor_with_project(project.clone(), buffer, window, cx)
11980    });
11981
11982    cx.executor().start_waiting();
11983    let fake_server = fake_servers.next().await.unwrap();
11984
11985    (project, editor, cx, fake_server)
11986}
11987
11988#[gpui::test]
11989async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11990    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11991
11992    editor.update_in(cx, |editor, window, cx| {
11993        editor.set_text("one\ntwo\nthree\n", window, cx)
11994    });
11995    assert!(cx.read(|cx| editor.is_dirty(cx)));
11996
11997    let save = editor
11998        .update_in(cx, |editor, window, cx| {
11999            editor.save(
12000                SaveOptions {
12001                    format: true,
12002                    autosave: false,
12003                },
12004                project.clone(),
12005                window,
12006                cx,
12007            )
12008        })
12009        .unwrap();
12010    fake_server
12011        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12012            assert_eq!(
12013                params.text_document.uri,
12014                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12015            );
12016            assert_eq!(params.options.tab_size, 4);
12017            Ok(Some(vec![lsp::TextEdit::new(
12018                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12019                ", ".to_string(),
12020            )]))
12021        })
12022        .next()
12023        .await;
12024    cx.executor().start_waiting();
12025    save.await;
12026    assert_eq!(
12027        editor.update(cx, |editor, cx| editor.text(cx)),
12028        "one, two\nthree\n"
12029    );
12030    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12031}
12032
12033#[gpui::test]
12034async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12035    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12036
12037    editor.update_in(cx, |editor, window, cx| {
12038        editor.set_text("one\ntwo\nthree\n", window, cx)
12039    });
12040    assert!(cx.read(|cx| editor.is_dirty(cx)));
12041
12042    // Test that save still works when formatting hangs
12043    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12044        move |params, _| async move {
12045            assert_eq!(
12046                params.text_document.uri,
12047                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12048            );
12049            futures::future::pending::<()>().await;
12050            unreachable!()
12051        },
12052    );
12053    let save = editor
12054        .update_in(cx, |editor, window, cx| {
12055            editor.save(
12056                SaveOptions {
12057                    format: true,
12058                    autosave: false,
12059                },
12060                project.clone(),
12061                window,
12062                cx,
12063            )
12064        })
12065        .unwrap();
12066    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12067    cx.executor().start_waiting();
12068    save.await;
12069    assert_eq!(
12070        editor.update(cx, |editor, cx| editor.text(cx)),
12071        "one\ntwo\nthree\n"
12072    );
12073    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12074}
12075
12076#[gpui::test]
12077async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12078    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12079
12080    // Buffer starts clean, no formatting should be requested
12081    let save = editor
12082        .update_in(cx, |editor, window, cx| {
12083            editor.save(
12084                SaveOptions {
12085                    format: false,
12086                    autosave: false,
12087                },
12088                project.clone(),
12089                window,
12090                cx,
12091            )
12092        })
12093        .unwrap();
12094    let _pending_format_request = fake_server
12095        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12096            panic!("Should not be invoked");
12097        })
12098        .next();
12099    cx.executor().start_waiting();
12100    save.await;
12101    cx.run_until_parked();
12102}
12103
12104#[gpui::test]
12105async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12106    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12107
12108    // Set Rust language override and assert overridden tabsize is sent to language server
12109    update_test_language_settings(cx, |settings| {
12110        settings.languages.0.insert(
12111            "Rust".into(),
12112            LanguageSettingsContent {
12113                tab_size: NonZeroU32::new(8),
12114                ..Default::default()
12115            },
12116        );
12117    });
12118
12119    editor.update_in(cx, |editor, window, cx| {
12120        editor.set_text("something_new\n", window, cx)
12121    });
12122    assert!(cx.read(|cx| editor.is_dirty(cx)));
12123    let save = editor
12124        .update_in(cx, |editor, window, cx| {
12125            editor.save(
12126                SaveOptions {
12127                    format: true,
12128                    autosave: false,
12129                },
12130                project.clone(),
12131                window,
12132                cx,
12133            )
12134        })
12135        .unwrap();
12136    fake_server
12137        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12138            assert_eq!(
12139                params.text_document.uri,
12140                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12141            );
12142            assert_eq!(params.options.tab_size, 8);
12143            Ok(Some(Vec::new()))
12144        })
12145        .next()
12146        .await;
12147    save.await;
12148}
12149
12150#[gpui::test]
12151async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12152    init_test(cx, |settings| {
12153        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12154            settings::LanguageServerFormatterSpecifier::Current,
12155        )))
12156    });
12157
12158    let fs = FakeFs::new(cx.executor());
12159    fs.insert_file(path!("/file.rs"), Default::default()).await;
12160
12161    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12162
12163    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12164    language_registry.add(Arc::new(Language::new(
12165        LanguageConfig {
12166            name: "Rust".into(),
12167            matcher: LanguageMatcher {
12168                path_suffixes: vec!["rs".to_string()],
12169                ..Default::default()
12170            },
12171            ..LanguageConfig::default()
12172        },
12173        Some(tree_sitter_rust::LANGUAGE.into()),
12174    )));
12175    update_test_language_settings(cx, |settings| {
12176        // Enable Prettier formatting for the same buffer, and ensure
12177        // LSP is called instead of Prettier.
12178        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12179    });
12180    let mut fake_servers = language_registry.register_fake_lsp(
12181        "Rust",
12182        FakeLspAdapter {
12183            capabilities: lsp::ServerCapabilities {
12184                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12185                ..Default::default()
12186            },
12187            ..Default::default()
12188        },
12189    );
12190
12191    let buffer = project
12192        .update(cx, |project, cx| {
12193            project.open_local_buffer(path!("/file.rs"), cx)
12194        })
12195        .await
12196        .unwrap();
12197
12198    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12199    let (editor, cx) = cx.add_window_view(|window, cx| {
12200        build_editor_with_project(project.clone(), buffer, window, cx)
12201    });
12202    editor.update_in(cx, |editor, window, cx| {
12203        editor.set_text("one\ntwo\nthree\n", window, cx)
12204    });
12205
12206    cx.executor().start_waiting();
12207    let fake_server = fake_servers.next().await.unwrap();
12208
12209    let format = editor
12210        .update_in(cx, |editor, window, cx| {
12211            editor.perform_format(
12212                project.clone(),
12213                FormatTrigger::Manual,
12214                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12215                window,
12216                cx,
12217            )
12218        })
12219        .unwrap();
12220    fake_server
12221        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12222            assert_eq!(
12223                params.text_document.uri,
12224                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12225            );
12226            assert_eq!(params.options.tab_size, 4);
12227            Ok(Some(vec![lsp::TextEdit::new(
12228                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12229                ", ".to_string(),
12230            )]))
12231        })
12232        .next()
12233        .await;
12234    cx.executor().start_waiting();
12235    format.await;
12236    assert_eq!(
12237        editor.update(cx, |editor, cx| editor.text(cx)),
12238        "one, two\nthree\n"
12239    );
12240
12241    editor.update_in(cx, |editor, window, cx| {
12242        editor.set_text("one\ntwo\nthree\n", window, cx)
12243    });
12244    // Ensure we don't lock if formatting hangs.
12245    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12246        move |params, _| async move {
12247            assert_eq!(
12248                params.text_document.uri,
12249                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12250            );
12251            futures::future::pending::<()>().await;
12252            unreachable!()
12253        },
12254    );
12255    let format = editor
12256        .update_in(cx, |editor, window, cx| {
12257            editor.perform_format(
12258                project,
12259                FormatTrigger::Manual,
12260                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12261                window,
12262                cx,
12263            )
12264        })
12265        .unwrap();
12266    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12267    cx.executor().start_waiting();
12268    format.await;
12269    assert_eq!(
12270        editor.update(cx, |editor, cx| editor.text(cx)),
12271        "one\ntwo\nthree\n"
12272    );
12273}
12274
12275#[gpui::test]
12276async fn test_multiple_formatters(cx: &mut TestAppContext) {
12277    init_test(cx, |settings| {
12278        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12279        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12280            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12281            Formatter::CodeAction("code-action-1".into()),
12282            Formatter::CodeAction("code-action-2".into()),
12283        ]))
12284    });
12285
12286    let fs = FakeFs::new(cx.executor());
12287    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12288        .await;
12289
12290    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12291    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12292    language_registry.add(rust_lang());
12293
12294    let mut fake_servers = language_registry.register_fake_lsp(
12295        "Rust",
12296        FakeLspAdapter {
12297            capabilities: lsp::ServerCapabilities {
12298                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12299                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12300                    commands: vec!["the-command-for-code-action-1".into()],
12301                    ..Default::default()
12302                }),
12303                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12304                ..Default::default()
12305            },
12306            ..Default::default()
12307        },
12308    );
12309
12310    let buffer = project
12311        .update(cx, |project, cx| {
12312            project.open_local_buffer(path!("/file.rs"), cx)
12313        })
12314        .await
12315        .unwrap();
12316
12317    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12318    let (editor, cx) = cx.add_window_view(|window, cx| {
12319        build_editor_with_project(project.clone(), buffer, window, cx)
12320    });
12321
12322    cx.executor().start_waiting();
12323
12324    let fake_server = fake_servers.next().await.unwrap();
12325    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12326        move |_params, _| async move {
12327            Ok(Some(vec![lsp::TextEdit::new(
12328                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12329                "applied-formatting\n".to_string(),
12330            )]))
12331        },
12332    );
12333    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12334        move |params, _| async move {
12335            let requested_code_actions = params.context.only.expect("Expected code action request");
12336            assert_eq!(requested_code_actions.len(), 1);
12337
12338            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12339            let code_action = match requested_code_actions[0].as_str() {
12340                "code-action-1" => lsp::CodeAction {
12341                    kind: Some("code-action-1".into()),
12342                    edit: Some(lsp::WorkspaceEdit::new(
12343                        [(
12344                            uri,
12345                            vec![lsp::TextEdit::new(
12346                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12347                                "applied-code-action-1-edit\n".to_string(),
12348                            )],
12349                        )]
12350                        .into_iter()
12351                        .collect(),
12352                    )),
12353                    command: Some(lsp::Command {
12354                        command: "the-command-for-code-action-1".into(),
12355                        ..Default::default()
12356                    }),
12357                    ..Default::default()
12358                },
12359                "code-action-2" => lsp::CodeAction {
12360                    kind: Some("code-action-2".into()),
12361                    edit: Some(lsp::WorkspaceEdit::new(
12362                        [(
12363                            uri,
12364                            vec![lsp::TextEdit::new(
12365                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12366                                "applied-code-action-2-edit\n".to_string(),
12367                            )],
12368                        )]
12369                        .into_iter()
12370                        .collect(),
12371                    )),
12372                    ..Default::default()
12373                },
12374                req => panic!("Unexpected code action request: {:?}", req),
12375            };
12376            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12377                code_action,
12378            )]))
12379        },
12380    );
12381
12382    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12383        move |params, _| async move { Ok(params) }
12384    });
12385
12386    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12387    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12388        let fake = fake_server.clone();
12389        let lock = command_lock.clone();
12390        move |params, _| {
12391            assert_eq!(params.command, "the-command-for-code-action-1");
12392            let fake = fake.clone();
12393            let lock = lock.clone();
12394            async move {
12395                lock.lock().await;
12396                fake.server
12397                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12398                        label: None,
12399                        edit: lsp::WorkspaceEdit {
12400                            changes: Some(
12401                                [(
12402                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12403                                    vec![lsp::TextEdit {
12404                                        range: lsp::Range::new(
12405                                            lsp::Position::new(0, 0),
12406                                            lsp::Position::new(0, 0),
12407                                        ),
12408                                        new_text: "applied-code-action-1-command\n".into(),
12409                                    }],
12410                                )]
12411                                .into_iter()
12412                                .collect(),
12413                            ),
12414                            ..Default::default()
12415                        },
12416                    })
12417                    .await
12418                    .into_response()
12419                    .unwrap();
12420                Ok(Some(json!(null)))
12421            }
12422        }
12423    });
12424
12425    cx.executor().start_waiting();
12426    editor
12427        .update_in(cx, |editor, window, cx| {
12428            editor.perform_format(
12429                project.clone(),
12430                FormatTrigger::Manual,
12431                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12432                window,
12433                cx,
12434            )
12435        })
12436        .unwrap()
12437        .await;
12438    editor.update(cx, |editor, cx| {
12439        assert_eq!(
12440            editor.text(cx),
12441            r#"
12442                applied-code-action-2-edit
12443                applied-code-action-1-command
12444                applied-code-action-1-edit
12445                applied-formatting
12446                one
12447                two
12448                three
12449            "#
12450            .unindent()
12451        );
12452    });
12453
12454    editor.update_in(cx, |editor, window, cx| {
12455        editor.undo(&Default::default(), window, cx);
12456        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12457    });
12458
12459    // Perform a manual edit while waiting for an LSP command
12460    // that's being run as part of a formatting code action.
12461    let lock_guard = command_lock.lock().await;
12462    let format = editor
12463        .update_in(cx, |editor, window, cx| {
12464            editor.perform_format(
12465                project.clone(),
12466                FormatTrigger::Manual,
12467                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12468                window,
12469                cx,
12470            )
12471        })
12472        .unwrap();
12473    cx.run_until_parked();
12474    editor.update(cx, |editor, cx| {
12475        assert_eq!(
12476            editor.text(cx),
12477            r#"
12478                applied-code-action-1-edit
12479                applied-formatting
12480                one
12481                two
12482                three
12483            "#
12484            .unindent()
12485        );
12486
12487        editor.buffer.update(cx, |buffer, cx| {
12488            let ix = buffer.len(cx);
12489            buffer.edit([(ix..ix, "edited\n")], None, cx);
12490        });
12491    });
12492
12493    // Allow the LSP command to proceed. Because the buffer was edited,
12494    // the second code action will not be run.
12495    drop(lock_guard);
12496    format.await;
12497    editor.update_in(cx, |editor, window, cx| {
12498        assert_eq!(
12499            editor.text(cx),
12500            r#"
12501                applied-code-action-1-command
12502                applied-code-action-1-edit
12503                applied-formatting
12504                one
12505                two
12506                three
12507                edited
12508            "#
12509            .unindent()
12510        );
12511
12512        // The manual edit is undone first, because it is the last thing the user did
12513        // (even though the command completed afterwards).
12514        editor.undo(&Default::default(), window, cx);
12515        assert_eq!(
12516            editor.text(cx),
12517            r#"
12518                applied-code-action-1-command
12519                applied-code-action-1-edit
12520                applied-formatting
12521                one
12522                two
12523                three
12524            "#
12525            .unindent()
12526        );
12527
12528        // All the formatting (including the command, which completed after the manual edit)
12529        // is undone together.
12530        editor.undo(&Default::default(), window, cx);
12531        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12532    });
12533}
12534
12535#[gpui::test]
12536async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12537    init_test(cx, |settings| {
12538        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12539            settings::LanguageServerFormatterSpecifier::Current,
12540        )]))
12541    });
12542
12543    let fs = FakeFs::new(cx.executor());
12544    fs.insert_file(path!("/file.ts"), Default::default()).await;
12545
12546    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12547
12548    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12549    language_registry.add(Arc::new(Language::new(
12550        LanguageConfig {
12551            name: "TypeScript".into(),
12552            matcher: LanguageMatcher {
12553                path_suffixes: vec!["ts".to_string()],
12554                ..Default::default()
12555            },
12556            ..LanguageConfig::default()
12557        },
12558        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12559    )));
12560    update_test_language_settings(cx, |settings| {
12561        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12562    });
12563    let mut fake_servers = language_registry.register_fake_lsp(
12564        "TypeScript",
12565        FakeLspAdapter {
12566            capabilities: lsp::ServerCapabilities {
12567                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12568                ..Default::default()
12569            },
12570            ..Default::default()
12571        },
12572    );
12573
12574    let buffer = project
12575        .update(cx, |project, cx| {
12576            project.open_local_buffer(path!("/file.ts"), cx)
12577        })
12578        .await
12579        .unwrap();
12580
12581    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12582    let (editor, cx) = cx.add_window_view(|window, cx| {
12583        build_editor_with_project(project.clone(), buffer, window, cx)
12584    });
12585    editor.update_in(cx, |editor, window, cx| {
12586        editor.set_text(
12587            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12588            window,
12589            cx,
12590        )
12591    });
12592
12593    cx.executor().start_waiting();
12594    let fake_server = fake_servers.next().await.unwrap();
12595
12596    let format = editor
12597        .update_in(cx, |editor, window, cx| {
12598            editor.perform_code_action_kind(
12599                project.clone(),
12600                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12601                window,
12602                cx,
12603            )
12604        })
12605        .unwrap();
12606    fake_server
12607        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12608            assert_eq!(
12609                params.text_document.uri,
12610                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12611            );
12612            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12613                lsp::CodeAction {
12614                    title: "Organize Imports".to_string(),
12615                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12616                    edit: Some(lsp::WorkspaceEdit {
12617                        changes: Some(
12618                            [(
12619                                params.text_document.uri.clone(),
12620                                vec![lsp::TextEdit::new(
12621                                    lsp::Range::new(
12622                                        lsp::Position::new(1, 0),
12623                                        lsp::Position::new(2, 0),
12624                                    ),
12625                                    "".to_string(),
12626                                )],
12627                            )]
12628                            .into_iter()
12629                            .collect(),
12630                        ),
12631                        ..Default::default()
12632                    }),
12633                    ..Default::default()
12634                },
12635            )]))
12636        })
12637        .next()
12638        .await;
12639    cx.executor().start_waiting();
12640    format.await;
12641    assert_eq!(
12642        editor.update(cx, |editor, cx| editor.text(cx)),
12643        "import { a } from 'module';\n\nconst x = a;\n"
12644    );
12645
12646    editor.update_in(cx, |editor, window, cx| {
12647        editor.set_text(
12648            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12649            window,
12650            cx,
12651        )
12652    });
12653    // Ensure we don't lock if code action hangs.
12654    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12655        move |params, _| async move {
12656            assert_eq!(
12657                params.text_document.uri,
12658                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12659            );
12660            futures::future::pending::<()>().await;
12661            unreachable!()
12662        },
12663    );
12664    let format = editor
12665        .update_in(cx, |editor, window, cx| {
12666            editor.perform_code_action_kind(
12667                project,
12668                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12669                window,
12670                cx,
12671            )
12672        })
12673        .unwrap();
12674    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12675    cx.executor().start_waiting();
12676    format.await;
12677    assert_eq!(
12678        editor.update(cx, |editor, cx| editor.text(cx)),
12679        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12680    );
12681}
12682
12683#[gpui::test]
12684async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12685    init_test(cx, |_| {});
12686
12687    let mut cx = EditorLspTestContext::new_rust(
12688        lsp::ServerCapabilities {
12689            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12690            ..Default::default()
12691        },
12692        cx,
12693    )
12694    .await;
12695
12696    cx.set_state(indoc! {"
12697        one.twoˇ
12698    "});
12699
12700    // The format request takes a long time. When it completes, it inserts
12701    // a newline and an indent before the `.`
12702    cx.lsp
12703        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12704            let executor = cx.background_executor().clone();
12705            async move {
12706                executor.timer(Duration::from_millis(100)).await;
12707                Ok(Some(vec![lsp::TextEdit {
12708                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12709                    new_text: "\n    ".into(),
12710                }]))
12711            }
12712        });
12713
12714    // Submit a format request.
12715    let format_1 = cx
12716        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12717        .unwrap();
12718    cx.executor().run_until_parked();
12719
12720    // Submit a second format request.
12721    let format_2 = cx
12722        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12723        .unwrap();
12724    cx.executor().run_until_parked();
12725
12726    // Wait for both format requests to complete
12727    cx.executor().advance_clock(Duration::from_millis(200));
12728    cx.executor().start_waiting();
12729    format_1.await.unwrap();
12730    cx.executor().start_waiting();
12731    format_2.await.unwrap();
12732
12733    // The formatting edits only happens once.
12734    cx.assert_editor_state(indoc! {"
12735        one
12736            .twoˇ
12737    "});
12738}
12739
12740#[gpui::test]
12741async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12742    init_test(cx, |settings| {
12743        settings.defaults.formatter = Some(FormatterList::default())
12744    });
12745
12746    let mut cx = EditorLspTestContext::new_rust(
12747        lsp::ServerCapabilities {
12748            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12749            ..Default::default()
12750        },
12751        cx,
12752    )
12753    .await;
12754
12755    // Record which buffer changes have been sent to the language server
12756    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12757    cx.lsp
12758        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12759            let buffer_changes = buffer_changes.clone();
12760            move |params, _| {
12761                buffer_changes.lock().extend(
12762                    params
12763                        .content_changes
12764                        .into_iter()
12765                        .map(|e| (e.range.unwrap(), e.text)),
12766                );
12767            }
12768        });
12769    // Handle formatting requests to the language server.
12770    cx.lsp
12771        .set_request_handler::<lsp::request::Formatting, _, _>({
12772            let buffer_changes = buffer_changes.clone();
12773            move |_, _| {
12774                let buffer_changes = buffer_changes.clone();
12775                // Insert blank lines between each line of the buffer.
12776                async move {
12777                    // When formatting is requested, trailing whitespace has already been stripped,
12778                    // and the trailing newline has already been added.
12779                    assert_eq!(
12780                        &buffer_changes.lock()[1..],
12781                        &[
12782                            (
12783                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12784                                "".into()
12785                            ),
12786                            (
12787                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12788                                "".into()
12789                            ),
12790                            (
12791                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12792                                "\n".into()
12793                            ),
12794                        ]
12795                    );
12796
12797                    Ok(Some(vec![
12798                        lsp::TextEdit {
12799                            range: lsp::Range::new(
12800                                lsp::Position::new(1, 0),
12801                                lsp::Position::new(1, 0),
12802                            ),
12803                            new_text: "\n".into(),
12804                        },
12805                        lsp::TextEdit {
12806                            range: lsp::Range::new(
12807                                lsp::Position::new(2, 0),
12808                                lsp::Position::new(2, 0),
12809                            ),
12810                            new_text: "\n".into(),
12811                        },
12812                    ]))
12813                }
12814            }
12815        });
12816
12817    // Set up a buffer white some trailing whitespace and no trailing newline.
12818    cx.set_state(
12819        &[
12820            "one ",   //
12821            "twoˇ",   //
12822            "three ", //
12823            "four",   //
12824        ]
12825        .join("\n"),
12826    );
12827    cx.run_until_parked();
12828
12829    // Submit a format request.
12830    let format = cx
12831        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12832        .unwrap();
12833
12834    cx.run_until_parked();
12835    // After formatting the buffer, the trailing whitespace is stripped,
12836    // a newline is appended, and the edits provided by the language server
12837    // have been applied.
12838    format.await.unwrap();
12839
12840    cx.assert_editor_state(
12841        &[
12842            "one",   //
12843            "",      //
12844            "twoˇ",  //
12845            "",      //
12846            "three", //
12847            "four",  //
12848            "",      //
12849        ]
12850        .join("\n"),
12851    );
12852
12853    // Undoing the formatting undoes the trailing whitespace removal, the
12854    // trailing newline, and the LSP edits.
12855    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12856    cx.assert_editor_state(
12857        &[
12858            "one ",   //
12859            "twoˇ",   //
12860            "three ", //
12861            "four",   //
12862        ]
12863        .join("\n"),
12864    );
12865}
12866
12867#[gpui::test]
12868async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12869    cx: &mut TestAppContext,
12870) {
12871    init_test(cx, |_| {});
12872
12873    cx.update(|cx| {
12874        cx.update_global::<SettingsStore, _>(|settings, cx| {
12875            settings.update_user_settings(cx, |settings| {
12876                settings.editor.auto_signature_help = Some(true);
12877            });
12878        });
12879    });
12880
12881    let mut cx = EditorLspTestContext::new_rust(
12882        lsp::ServerCapabilities {
12883            signature_help_provider: Some(lsp::SignatureHelpOptions {
12884                ..Default::default()
12885            }),
12886            ..Default::default()
12887        },
12888        cx,
12889    )
12890    .await;
12891
12892    let language = Language::new(
12893        LanguageConfig {
12894            name: "Rust".into(),
12895            brackets: BracketPairConfig {
12896                pairs: vec![
12897                    BracketPair {
12898                        start: "{".to_string(),
12899                        end: "}".to_string(),
12900                        close: true,
12901                        surround: true,
12902                        newline: true,
12903                    },
12904                    BracketPair {
12905                        start: "(".to_string(),
12906                        end: ")".to_string(),
12907                        close: true,
12908                        surround: true,
12909                        newline: true,
12910                    },
12911                    BracketPair {
12912                        start: "/*".to_string(),
12913                        end: " */".to_string(),
12914                        close: true,
12915                        surround: true,
12916                        newline: true,
12917                    },
12918                    BracketPair {
12919                        start: "[".to_string(),
12920                        end: "]".to_string(),
12921                        close: false,
12922                        surround: false,
12923                        newline: true,
12924                    },
12925                    BracketPair {
12926                        start: "\"".to_string(),
12927                        end: "\"".to_string(),
12928                        close: true,
12929                        surround: true,
12930                        newline: false,
12931                    },
12932                    BracketPair {
12933                        start: "<".to_string(),
12934                        end: ">".to_string(),
12935                        close: false,
12936                        surround: true,
12937                        newline: true,
12938                    },
12939                ],
12940                ..Default::default()
12941            },
12942            autoclose_before: "})]".to_string(),
12943            ..Default::default()
12944        },
12945        Some(tree_sitter_rust::LANGUAGE.into()),
12946    );
12947    let language = Arc::new(language);
12948
12949    cx.language_registry().add(language.clone());
12950    cx.update_buffer(|buffer, cx| {
12951        buffer.set_language(Some(language), cx);
12952    });
12953
12954    cx.set_state(
12955        &r#"
12956            fn main() {
12957                sampleˇ
12958            }
12959        "#
12960        .unindent(),
12961    );
12962
12963    cx.update_editor(|editor, window, cx| {
12964        editor.handle_input("(", window, cx);
12965    });
12966    cx.assert_editor_state(
12967        &"
12968            fn main() {
12969                sample(ˇ)
12970            }
12971        "
12972        .unindent(),
12973    );
12974
12975    let mocked_response = lsp::SignatureHelp {
12976        signatures: vec![lsp::SignatureInformation {
12977            label: "fn sample(param1: u8, param2: u8)".to_string(),
12978            documentation: None,
12979            parameters: Some(vec![
12980                lsp::ParameterInformation {
12981                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12982                    documentation: None,
12983                },
12984                lsp::ParameterInformation {
12985                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12986                    documentation: None,
12987                },
12988            ]),
12989            active_parameter: None,
12990        }],
12991        active_signature: Some(0),
12992        active_parameter: Some(0),
12993    };
12994    handle_signature_help_request(&mut cx, mocked_response).await;
12995
12996    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12997        .await;
12998
12999    cx.editor(|editor, _, _| {
13000        let signature_help_state = editor.signature_help_state.popover().cloned();
13001        let signature = signature_help_state.unwrap();
13002        assert_eq!(
13003            signature.signatures[signature.current_signature].label,
13004            "fn sample(param1: u8, param2: u8)"
13005        );
13006    });
13007}
13008
13009#[gpui::test]
13010async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13011    init_test(cx, |_| {});
13012
13013    cx.update(|cx| {
13014        cx.update_global::<SettingsStore, _>(|settings, cx| {
13015            settings.update_user_settings(cx, |settings| {
13016                settings.editor.auto_signature_help = Some(false);
13017                settings.editor.show_signature_help_after_edits = Some(false);
13018            });
13019        });
13020    });
13021
13022    let mut cx = EditorLspTestContext::new_rust(
13023        lsp::ServerCapabilities {
13024            signature_help_provider: Some(lsp::SignatureHelpOptions {
13025                ..Default::default()
13026            }),
13027            ..Default::default()
13028        },
13029        cx,
13030    )
13031    .await;
13032
13033    let language = Language::new(
13034        LanguageConfig {
13035            name: "Rust".into(),
13036            brackets: BracketPairConfig {
13037                pairs: vec![
13038                    BracketPair {
13039                        start: "{".to_string(),
13040                        end: "}".to_string(),
13041                        close: true,
13042                        surround: true,
13043                        newline: true,
13044                    },
13045                    BracketPair {
13046                        start: "(".to_string(),
13047                        end: ")".to_string(),
13048                        close: true,
13049                        surround: true,
13050                        newline: true,
13051                    },
13052                    BracketPair {
13053                        start: "/*".to_string(),
13054                        end: " */".to_string(),
13055                        close: true,
13056                        surround: true,
13057                        newline: true,
13058                    },
13059                    BracketPair {
13060                        start: "[".to_string(),
13061                        end: "]".to_string(),
13062                        close: false,
13063                        surround: false,
13064                        newline: true,
13065                    },
13066                    BracketPair {
13067                        start: "\"".to_string(),
13068                        end: "\"".to_string(),
13069                        close: true,
13070                        surround: true,
13071                        newline: false,
13072                    },
13073                    BracketPair {
13074                        start: "<".to_string(),
13075                        end: ">".to_string(),
13076                        close: false,
13077                        surround: true,
13078                        newline: true,
13079                    },
13080                ],
13081                ..Default::default()
13082            },
13083            autoclose_before: "})]".to_string(),
13084            ..Default::default()
13085        },
13086        Some(tree_sitter_rust::LANGUAGE.into()),
13087    );
13088    let language = Arc::new(language);
13089
13090    cx.language_registry().add(language.clone());
13091    cx.update_buffer(|buffer, cx| {
13092        buffer.set_language(Some(language), cx);
13093    });
13094
13095    // Ensure that signature_help is not called when no signature help is enabled.
13096    cx.set_state(
13097        &r#"
13098            fn main() {
13099                sampleˇ
13100            }
13101        "#
13102        .unindent(),
13103    );
13104    cx.update_editor(|editor, window, cx| {
13105        editor.handle_input("(", window, cx);
13106    });
13107    cx.assert_editor_state(
13108        &"
13109            fn main() {
13110                sample(ˇ)
13111            }
13112        "
13113        .unindent(),
13114    );
13115    cx.editor(|editor, _, _| {
13116        assert!(editor.signature_help_state.task().is_none());
13117    });
13118
13119    let mocked_response = lsp::SignatureHelp {
13120        signatures: vec![lsp::SignatureInformation {
13121            label: "fn sample(param1: u8, param2: u8)".to_string(),
13122            documentation: None,
13123            parameters: Some(vec![
13124                lsp::ParameterInformation {
13125                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13126                    documentation: None,
13127                },
13128                lsp::ParameterInformation {
13129                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13130                    documentation: None,
13131                },
13132            ]),
13133            active_parameter: None,
13134        }],
13135        active_signature: Some(0),
13136        active_parameter: Some(0),
13137    };
13138
13139    // Ensure that signature_help is called when enabled afte edits
13140    cx.update(|_, cx| {
13141        cx.update_global::<SettingsStore, _>(|settings, cx| {
13142            settings.update_user_settings(cx, |settings| {
13143                settings.editor.auto_signature_help = Some(false);
13144                settings.editor.show_signature_help_after_edits = Some(true);
13145            });
13146        });
13147    });
13148    cx.set_state(
13149        &r#"
13150            fn main() {
13151                sampleˇ
13152            }
13153        "#
13154        .unindent(),
13155    );
13156    cx.update_editor(|editor, window, cx| {
13157        editor.handle_input("(", window, cx);
13158    });
13159    cx.assert_editor_state(
13160        &"
13161            fn main() {
13162                sample(ˇ)
13163            }
13164        "
13165        .unindent(),
13166    );
13167    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13168    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13169        .await;
13170    cx.update_editor(|editor, _, _| {
13171        let signature_help_state = editor.signature_help_state.popover().cloned();
13172        assert!(signature_help_state.is_some());
13173        let signature = signature_help_state.unwrap();
13174        assert_eq!(
13175            signature.signatures[signature.current_signature].label,
13176            "fn sample(param1: u8, param2: u8)"
13177        );
13178        editor.signature_help_state = SignatureHelpState::default();
13179    });
13180
13181    // Ensure that signature_help is called when auto signature help override is enabled
13182    cx.update(|_, cx| {
13183        cx.update_global::<SettingsStore, _>(|settings, cx| {
13184            settings.update_user_settings(cx, |settings| {
13185                settings.editor.auto_signature_help = Some(true);
13186                settings.editor.show_signature_help_after_edits = Some(false);
13187            });
13188        });
13189    });
13190    cx.set_state(
13191        &r#"
13192            fn main() {
13193                sampleˇ
13194            }
13195        "#
13196        .unindent(),
13197    );
13198    cx.update_editor(|editor, window, cx| {
13199        editor.handle_input("(", window, cx);
13200    });
13201    cx.assert_editor_state(
13202        &"
13203            fn main() {
13204                sample(ˇ)
13205            }
13206        "
13207        .unindent(),
13208    );
13209    handle_signature_help_request(&mut cx, mocked_response).await;
13210    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13211        .await;
13212    cx.editor(|editor, _, _| {
13213        let signature_help_state = editor.signature_help_state.popover().cloned();
13214        assert!(signature_help_state.is_some());
13215        let signature = signature_help_state.unwrap();
13216        assert_eq!(
13217            signature.signatures[signature.current_signature].label,
13218            "fn sample(param1: u8, param2: u8)"
13219        );
13220    });
13221}
13222
13223#[gpui::test]
13224async fn test_signature_help(cx: &mut TestAppContext) {
13225    init_test(cx, |_| {});
13226    cx.update(|cx| {
13227        cx.update_global::<SettingsStore, _>(|settings, cx| {
13228            settings.update_user_settings(cx, |settings| {
13229                settings.editor.auto_signature_help = Some(true);
13230            });
13231        });
13232    });
13233
13234    let mut cx = EditorLspTestContext::new_rust(
13235        lsp::ServerCapabilities {
13236            signature_help_provider: Some(lsp::SignatureHelpOptions {
13237                ..Default::default()
13238            }),
13239            ..Default::default()
13240        },
13241        cx,
13242    )
13243    .await;
13244
13245    // A test that directly calls `show_signature_help`
13246    cx.update_editor(|editor, window, cx| {
13247        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13248    });
13249
13250    let mocked_response = lsp::SignatureHelp {
13251        signatures: vec![lsp::SignatureInformation {
13252            label: "fn sample(param1: u8, param2: u8)".to_string(),
13253            documentation: None,
13254            parameters: Some(vec![
13255                lsp::ParameterInformation {
13256                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13257                    documentation: None,
13258                },
13259                lsp::ParameterInformation {
13260                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13261                    documentation: None,
13262                },
13263            ]),
13264            active_parameter: None,
13265        }],
13266        active_signature: Some(0),
13267        active_parameter: Some(0),
13268    };
13269    handle_signature_help_request(&mut cx, mocked_response).await;
13270
13271    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13272        .await;
13273
13274    cx.editor(|editor, _, _| {
13275        let signature_help_state = editor.signature_help_state.popover().cloned();
13276        assert!(signature_help_state.is_some());
13277        let signature = signature_help_state.unwrap();
13278        assert_eq!(
13279            signature.signatures[signature.current_signature].label,
13280            "fn sample(param1: u8, param2: u8)"
13281        );
13282    });
13283
13284    // When exiting outside from inside the brackets, `signature_help` is closed.
13285    cx.set_state(indoc! {"
13286        fn main() {
13287            sample(ˇ);
13288        }
13289
13290        fn sample(param1: u8, param2: u8) {}
13291    "});
13292
13293    cx.update_editor(|editor, window, cx| {
13294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13295            s.select_ranges([0..0])
13296        });
13297    });
13298
13299    let mocked_response = lsp::SignatureHelp {
13300        signatures: Vec::new(),
13301        active_signature: None,
13302        active_parameter: None,
13303    };
13304    handle_signature_help_request(&mut cx, mocked_response).await;
13305
13306    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13307        .await;
13308
13309    cx.editor(|editor, _, _| {
13310        assert!(!editor.signature_help_state.is_shown());
13311    });
13312
13313    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13314    cx.set_state(indoc! {"
13315        fn main() {
13316            sample(ˇ);
13317        }
13318
13319        fn sample(param1: u8, param2: u8) {}
13320    "});
13321
13322    let mocked_response = lsp::SignatureHelp {
13323        signatures: vec![lsp::SignatureInformation {
13324            label: "fn sample(param1: u8, param2: u8)".to_string(),
13325            documentation: None,
13326            parameters: Some(vec![
13327                lsp::ParameterInformation {
13328                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13329                    documentation: None,
13330                },
13331                lsp::ParameterInformation {
13332                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13333                    documentation: None,
13334                },
13335            ]),
13336            active_parameter: None,
13337        }],
13338        active_signature: Some(0),
13339        active_parameter: Some(0),
13340    };
13341    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13342    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13343        .await;
13344    cx.editor(|editor, _, _| {
13345        assert!(editor.signature_help_state.is_shown());
13346    });
13347
13348    // Restore the popover with more parameter input
13349    cx.set_state(indoc! {"
13350        fn main() {
13351            sample(param1, param2ˇ);
13352        }
13353
13354        fn sample(param1: u8, param2: u8) {}
13355    "});
13356
13357    let mocked_response = lsp::SignatureHelp {
13358        signatures: vec![lsp::SignatureInformation {
13359            label: "fn sample(param1: u8, param2: u8)".to_string(),
13360            documentation: None,
13361            parameters: Some(vec![
13362                lsp::ParameterInformation {
13363                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13364                    documentation: None,
13365                },
13366                lsp::ParameterInformation {
13367                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13368                    documentation: None,
13369                },
13370            ]),
13371            active_parameter: None,
13372        }],
13373        active_signature: Some(0),
13374        active_parameter: Some(1),
13375    };
13376    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13377    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13378        .await;
13379
13380    // When selecting a range, the popover is gone.
13381    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13382    cx.update_editor(|editor, window, cx| {
13383        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13384            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13385        })
13386    });
13387    cx.assert_editor_state(indoc! {"
13388        fn main() {
13389            sample(param1, «ˇparam2»);
13390        }
13391
13392        fn sample(param1: u8, param2: u8) {}
13393    "});
13394    cx.editor(|editor, _, _| {
13395        assert!(!editor.signature_help_state.is_shown());
13396    });
13397
13398    // When unselecting again, the popover is back if within the brackets.
13399    cx.update_editor(|editor, window, cx| {
13400        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13401            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13402        })
13403    });
13404    cx.assert_editor_state(indoc! {"
13405        fn main() {
13406            sample(param1, ˇparam2);
13407        }
13408
13409        fn sample(param1: u8, param2: u8) {}
13410    "});
13411    handle_signature_help_request(&mut cx, mocked_response).await;
13412    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13413        .await;
13414    cx.editor(|editor, _, _| {
13415        assert!(editor.signature_help_state.is_shown());
13416    });
13417
13418    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13419    cx.update_editor(|editor, window, cx| {
13420        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13421            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13422            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13423        })
13424    });
13425    cx.assert_editor_state(indoc! {"
13426        fn main() {
13427            sample(param1, ˇparam2);
13428        }
13429
13430        fn sample(param1: u8, param2: u8) {}
13431    "});
13432
13433    let mocked_response = lsp::SignatureHelp {
13434        signatures: vec![lsp::SignatureInformation {
13435            label: "fn sample(param1: u8, param2: u8)".to_string(),
13436            documentation: None,
13437            parameters: Some(vec![
13438                lsp::ParameterInformation {
13439                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13440                    documentation: None,
13441                },
13442                lsp::ParameterInformation {
13443                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13444                    documentation: None,
13445                },
13446            ]),
13447            active_parameter: None,
13448        }],
13449        active_signature: Some(0),
13450        active_parameter: Some(1),
13451    };
13452    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13453    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13454        .await;
13455    cx.update_editor(|editor, _, cx| {
13456        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13457    });
13458    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13459        .await;
13460    cx.update_editor(|editor, window, cx| {
13461        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13462            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13463        })
13464    });
13465    cx.assert_editor_state(indoc! {"
13466        fn main() {
13467            sample(param1, «ˇparam2»);
13468        }
13469
13470        fn sample(param1: u8, param2: u8) {}
13471    "});
13472    cx.update_editor(|editor, window, cx| {
13473        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13474            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13475        })
13476    });
13477    cx.assert_editor_state(indoc! {"
13478        fn main() {
13479            sample(param1, ˇparam2);
13480        }
13481
13482        fn sample(param1: u8, param2: u8) {}
13483    "});
13484    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13485        .await;
13486}
13487
13488#[gpui::test]
13489async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13490    init_test(cx, |_| {});
13491
13492    let mut cx = EditorLspTestContext::new_rust(
13493        lsp::ServerCapabilities {
13494            signature_help_provider: Some(lsp::SignatureHelpOptions {
13495                ..Default::default()
13496            }),
13497            ..Default::default()
13498        },
13499        cx,
13500    )
13501    .await;
13502
13503    cx.set_state(indoc! {"
13504        fn main() {
13505            overloadedˇ
13506        }
13507    "});
13508
13509    cx.update_editor(|editor, window, cx| {
13510        editor.handle_input("(", window, cx);
13511        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13512    });
13513
13514    // Mock response with 3 signatures
13515    let mocked_response = lsp::SignatureHelp {
13516        signatures: vec![
13517            lsp::SignatureInformation {
13518                label: "fn overloaded(x: i32)".to_string(),
13519                documentation: None,
13520                parameters: Some(vec![lsp::ParameterInformation {
13521                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13522                    documentation: None,
13523                }]),
13524                active_parameter: None,
13525            },
13526            lsp::SignatureInformation {
13527                label: "fn overloaded(x: i32, y: i32)".to_string(),
13528                documentation: None,
13529                parameters: Some(vec![
13530                    lsp::ParameterInformation {
13531                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13532                        documentation: None,
13533                    },
13534                    lsp::ParameterInformation {
13535                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13536                        documentation: None,
13537                    },
13538                ]),
13539                active_parameter: None,
13540            },
13541            lsp::SignatureInformation {
13542                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13543                documentation: None,
13544                parameters: Some(vec![
13545                    lsp::ParameterInformation {
13546                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13547                        documentation: None,
13548                    },
13549                    lsp::ParameterInformation {
13550                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13551                        documentation: None,
13552                    },
13553                    lsp::ParameterInformation {
13554                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13555                        documentation: None,
13556                    },
13557                ]),
13558                active_parameter: None,
13559            },
13560        ],
13561        active_signature: Some(1),
13562        active_parameter: Some(0),
13563    };
13564    handle_signature_help_request(&mut cx, mocked_response).await;
13565
13566    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13567        .await;
13568
13569    // Verify we have multiple signatures and the right one is selected
13570    cx.editor(|editor, _, _| {
13571        let popover = editor.signature_help_state.popover().cloned().unwrap();
13572        assert_eq!(popover.signatures.len(), 3);
13573        // active_signature was 1, so that should be the current
13574        assert_eq!(popover.current_signature, 1);
13575        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13576        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13577        assert_eq!(
13578            popover.signatures[2].label,
13579            "fn overloaded(x: i32, y: i32, z: i32)"
13580        );
13581    });
13582
13583    // Test navigation functionality
13584    cx.update_editor(|editor, window, cx| {
13585        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13586    });
13587
13588    cx.editor(|editor, _, _| {
13589        let popover = editor.signature_help_state.popover().cloned().unwrap();
13590        assert_eq!(popover.current_signature, 2);
13591    });
13592
13593    // Test wrap around
13594    cx.update_editor(|editor, window, cx| {
13595        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13596    });
13597
13598    cx.editor(|editor, _, _| {
13599        let popover = editor.signature_help_state.popover().cloned().unwrap();
13600        assert_eq!(popover.current_signature, 0);
13601    });
13602
13603    // Test previous navigation
13604    cx.update_editor(|editor, window, cx| {
13605        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13606    });
13607
13608    cx.editor(|editor, _, _| {
13609        let popover = editor.signature_help_state.popover().cloned().unwrap();
13610        assert_eq!(popover.current_signature, 2);
13611    });
13612}
13613
13614#[gpui::test]
13615async fn test_completion_mode(cx: &mut TestAppContext) {
13616    init_test(cx, |_| {});
13617    let mut cx = EditorLspTestContext::new_rust(
13618        lsp::ServerCapabilities {
13619            completion_provider: Some(lsp::CompletionOptions {
13620                resolve_provider: Some(true),
13621                ..Default::default()
13622            }),
13623            ..Default::default()
13624        },
13625        cx,
13626    )
13627    .await;
13628
13629    struct Run {
13630        run_description: &'static str,
13631        initial_state: String,
13632        buffer_marked_text: String,
13633        completion_label: &'static str,
13634        completion_text: &'static str,
13635        expected_with_insert_mode: String,
13636        expected_with_replace_mode: String,
13637        expected_with_replace_subsequence_mode: String,
13638        expected_with_replace_suffix_mode: String,
13639    }
13640
13641    let runs = [
13642        Run {
13643            run_description: "Start of word matches completion text",
13644            initial_state: "before ediˇ after".into(),
13645            buffer_marked_text: "before <edi|> after".into(),
13646            completion_label: "editor",
13647            completion_text: "editor",
13648            expected_with_insert_mode: "before editorˇ after".into(),
13649            expected_with_replace_mode: "before editorˇ after".into(),
13650            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13651            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13652        },
13653        Run {
13654            run_description: "Accept same text at the middle of the word",
13655            initial_state: "before ediˇtor after".into(),
13656            buffer_marked_text: "before <edi|tor> after".into(),
13657            completion_label: "editor",
13658            completion_text: "editor",
13659            expected_with_insert_mode: "before editorˇtor after".into(),
13660            expected_with_replace_mode: "before editorˇ after".into(),
13661            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13662            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13663        },
13664        Run {
13665            run_description: "End of word matches completion text -- cursor at end",
13666            initial_state: "before torˇ after".into(),
13667            buffer_marked_text: "before <tor|> after".into(),
13668            completion_label: "editor",
13669            completion_text: "editor",
13670            expected_with_insert_mode: "before editorˇ after".into(),
13671            expected_with_replace_mode: "before editorˇ after".into(),
13672            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13673            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13674        },
13675        Run {
13676            run_description: "End of word matches completion text -- cursor at start",
13677            initial_state: "before ˇtor after".into(),
13678            buffer_marked_text: "before <|tor> after".into(),
13679            completion_label: "editor",
13680            completion_text: "editor",
13681            expected_with_insert_mode: "before editorˇtor after".into(),
13682            expected_with_replace_mode: "before editorˇ after".into(),
13683            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13684            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13685        },
13686        Run {
13687            run_description: "Prepend text containing whitespace",
13688            initial_state: "pˇfield: bool".into(),
13689            buffer_marked_text: "<p|field>: bool".into(),
13690            completion_label: "pub ",
13691            completion_text: "pub ",
13692            expected_with_insert_mode: "pub ˇfield: bool".into(),
13693            expected_with_replace_mode: "pub ˇ: bool".into(),
13694            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13695            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13696        },
13697        Run {
13698            run_description: "Add element to start of list",
13699            initial_state: "[element_ˇelement_2]".into(),
13700            buffer_marked_text: "[<element_|element_2>]".into(),
13701            completion_label: "element_1",
13702            completion_text: "element_1",
13703            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13704            expected_with_replace_mode: "[element_1ˇ]".into(),
13705            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13706            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13707        },
13708        Run {
13709            run_description: "Add element to start of list -- first and second elements are equal",
13710            initial_state: "[elˇelement]".into(),
13711            buffer_marked_text: "[<el|element>]".into(),
13712            completion_label: "element",
13713            completion_text: "element",
13714            expected_with_insert_mode: "[elementˇelement]".into(),
13715            expected_with_replace_mode: "[elementˇ]".into(),
13716            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13717            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13718        },
13719        Run {
13720            run_description: "Ends with matching suffix",
13721            initial_state: "SubˇError".into(),
13722            buffer_marked_text: "<Sub|Error>".into(),
13723            completion_label: "SubscriptionError",
13724            completion_text: "SubscriptionError",
13725            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13726            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13727            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13728            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13729        },
13730        Run {
13731            run_description: "Suffix is a subsequence -- contiguous",
13732            initial_state: "SubˇErr".into(),
13733            buffer_marked_text: "<Sub|Err>".into(),
13734            completion_label: "SubscriptionError",
13735            completion_text: "SubscriptionError",
13736            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13737            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13738            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13739            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13740        },
13741        Run {
13742            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13743            initial_state: "Suˇscrirr".into(),
13744            buffer_marked_text: "<Su|scrirr>".into(),
13745            completion_label: "SubscriptionError",
13746            completion_text: "SubscriptionError",
13747            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13748            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13749            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13750            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13751        },
13752        Run {
13753            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13754            initial_state: "foo(indˇix)".into(),
13755            buffer_marked_text: "foo(<ind|ix>)".into(),
13756            completion_label: "node_index",
13757            completion_text: "node_index",
13758            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13759            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13760            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13761            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13762        },
13763        Run {
13764            run_description: "Replace range ends before cursor - should extend to cursor",
13765            initial_state: "before editˇo after".into(),
13766            buffer_marked_text: "before <{ed}>it|o after".into(),
13767            completion_label: "editor",
13768            completion_text: "editor",
13769            expected_with_insert_mode: "before editorˇo after".into(),
13770            expected_with_replace_mode: "before editorˇo after".into(),
13771            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13772            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13773        },
13774        Run {
13775            run_description: "Uses label for suffix matching",
13776            initial_state: "before ediˇtor after".into(),
13777            buffer_marked_text: "before <edi|tor> after".into(),
13778            completion_label: "editor",
13779            completion_text: "editor()",
13780            expected_with_insert_mode: "before editor()ˇtor after".into(),
13781            expected_with_replace_mode: "before editor()ˇ after".into(),
13782            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13783            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13784        },
13785        Run {
13786            run_description: "Case insensitive subsequence and suffix matching",
13787            initial_state: "before EDiˇtoR after".into(),
13788            buffer_marked_text: "before <EDi|toR> after".into(),
13789            completion_label: "editor",
13790            completion_text: "editor",
13791            expected_with_insert_mode: "before editorˇtoR after".into(),
13792            expected_with_replace_mode: "before editorˇ after".into(),
13793            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13794            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13795        },
13796    ];
13797
13798    for run in runs {
13799        let run_variations = [
13800            (LspInsertMode::Insert, run.expected_with_insert_mode),
13801            (LspInsertMode::Replace, run.expected_with_replace_mode),
13802            (
13803                LspInsertMode::ReplaceSubsequence,
13804                run.expected_with_replace_subsequence_mode,
13805            ),
13806            (
13807                LspInsertMode::ReplaceSuffix,
13808                run.expected_with_replace_suffix_mode,
13809            ),
13810        ];
13811
13812        for (lsp_insert_mode, expected_text) in run_variations {
13813            eprintln!(
13814                "run = {:?}, mode = {lsp_insert_mode:.?}",
13815                run.run_description,
13816            );
13817
13818            update_test_language_settings(&mut cx, |settings| {
13819                settings.defaults.completions = Some(CompletionSettingsContent {
13820                    lsp_insert_mode: Some(lsp_insert_mode),
13821                    words: Some(WordsCompletionMode::Disabled),
13822                    words_min_length: Some(0),
13823                    ..Default::default()
13824                });
13825            });
13826
13827            cx.set_state(&run.initial_state);
13828            cx.update_editor(|editor, window, cx| {
13829                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13830            });
13831
13832            let counter = Arc::new(AtomicUsize::new(0));
13833            handle_completion_request_with_insert_and_replace(
13834                &mut cx,
13835                &run.buffer_marked_text,
13836                vec![(run.completion_label, run.completion_text)],
13837                counter.clone(),
13838            )
13839            .await;
13840            cx.condition(|editor, _| editor.context_menu_visible())
13841                .await;
13842            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13843
13844            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13845                editor
13846                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13847                    .unwrap()
13848            });
13849            cx.assert_editor_state(&expected_text);
13850            handle_resolve_completion_request(&mut cx, None).await;
13851            apply_additional_edits.await.unwrap();
13852        }
13853    }
13854}
13855
13856#[gpui::test]
13857async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13858    init_test(cx, |_| {});
13859    let mut cx = EditorLspTestContext::new_rust(
13860        lsp::ServerCapabilities {
13861            completion_provider: Some(lsp::CompletionOptions {
13862                resolve_provider: Some(true),
13863                ..Default::default()
13864            }),
13865            ..Default::default()
13866        },
13867        cx,
13868    )
13869    .await;
13870
13871    let initial_state = "SubˇError";
13872    let buffer_marked_text = "<Sub|Error>";
13873    let completion_text = "SubscriptionError";
13874    let expected_with_insert_mode = "SubscriptionErrorˇError";
13875    let expected_with_replace_mode = "SubscriptionErrorˇ";
13876
13877    update_test_language_settings(&mut cx, |settings| {
13878        settings.defaults.completions = Some(CompletionSettingsContent {
13879            words: Some(WordsCompletionMode::Disabled),
13880            words_min_length: Some(0),
13881            // set the opposite here to ensure that the action is overriding the default behavior
13882            lsp_insert_mode: Some(LspInsertMode::Insert),
13883            ..Default::default()
13884        });
13885    });
13886
13887    cx.set_state(initial_state);
13888    cx.update_editor(|editor, window, cx| {
13889        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13890    });
13891
13892    let counter = Arc::new(AtomicUsize::new(0));
13893    handle_completion_request_with_insert_and_replace(
13894        &mut cx,
13895        buffer_marked_text,
13896        vec![(completion_text, completion_text)],
13897        counter.clone(),
13898    )
13899    .await;
13900    cx.condition(|editor, _| editor.context_menu_visible())
13901        .await;
13902    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13903
13904    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13905        editor
13906            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13907            .unwrap()
13908    });
13909    cx.assert_editor_state(expected_with_replace_mode);
13910    handle_resolve_completion_request(&mut cx, None).await;
13911    apply_additional_edits.await.unwrap();
13912
13913    update_test_language_settings(&mut cx, |settings| {
13914        settings.defaults.completions = Some(CompletionSettingsContent {
13915            words: Some(WordsCompletionMode::Disabled),
13916            words_min_length: Some(0),
13917            // set the opposite here to ensure that the action is overriding the default behavior
13918            lsp_insert_mode: Some(LspInsertMode::Replace),
13919            ..Default::default()
13920        });
13921    });
13922
13923    cx.set_state(initial_state);
13924    cx.update_editor(|editor, window, cx| {
13925        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13926    });
13927    handle_completion_request_with_insert_and_replace(
13928        &mut cx,
13929        buffer_marked_text,
13930        vec![(completion_text, completion_text)],
13931        counter.clone(),
13932    )
13933    .await;
13934    cx.condition(|editor, _| editor.context_menu_visible())
13935        .await;
13936    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13937
13938    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13939        editor
13940            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13941            .unwrap()
13942    });
13943    cx.assert_editor_state(expected_with_insert_mode);
13944    handle_resolve_completion_request(&mut cx, None).await;
13945    apply_additional_edits.await.unwrap();
13946}
13947
13948#[gpui::test]
13949async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13950    init_test(cx, |_| {});
13951    let mut cx = EditorLspTestContext::new_rust(
13952        lsp::ServerCapabilities {
13953            completion_provider: Some(lsp::CompletionOptions {
13954                resolve_provider: Some(true),
13955                ..Default::default()
13956            }),
13957            ..Default::default()
13958        },
13959        cx,
13960    )
13961    .await;
13962
13963    // scenario: surrounding text matches completion text
13964    let completion_text = "to_offset";
13965    let initial_state = indoc! {"
13966        1. buf.to_offˇsuffix
13967        2. buf.to_offˇsuf
13968        3. buf.to_offˇfix
13969        4. buf.to_offˇ
13970        5. into_offˇensive
13971        6. ˇsuffix
13972        7. let ˇ //
13973        8. aaˇzz
13974        9. buf.to_off«zzzzzˇ»suffix
13975        10. buf.«ˇzzzzz»suffix
13976        11. to_off«ˇzzzzz»
13977
13978        buf.to_offˇsuffix  // newest cursor
13979    "};
13980    let completion_marked_buffer = indoc! {"
13981        1. buf.to_offsuffix
13982        2. buf.to_offsuf
13983        3. buf.to_offfix
13984        4. buf.to_off
13985        5. into_offensive
13986        6. suffix
13987        7. let  //
13988        8. aazz
13989        9. buf.to_offzzzzzsuffix
13990        10. buf.zzzzzsuffix
13991        11. to_offzzzzz
13992
13993        buf.<to_off|suffix>  // newest cursor
13994    "};
13995    let expected = indoc! {"
13996        1. buf.to_offsetˇ
13997        2. buf.to_offsetˇsuf
13998        3. buf.to_offsetˇfix
13999        4. buf.to_offsetˇ
14000        5. into_offsetˇensive
14001        6. to_offsetˇsuffix
14002        7. let to_offsetˇ //
14003        8. aato_offsetˇzz
14004        9. buf.to_offsetˇ
14005        10. buf.to_offsetˇsuffix
14006        11. to_offsetˇ
14007
14008        buf.to_offsetˇ  // newest cursor
14009    "};
14010    cx.set_state(initial_state);
14011    cx.update_editor(|editor, window, cx| {
14012        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14013    });
14014    handle_completion_request_with_insert_and_replace(
14015        &mut cx,
14016        completion_marked_buffer,
14017        vec![(completion_text, completion_text)],
14018        Arc::new(AtomicUsize::new(0)),
14019    )
14020    .await;
14021    cx.condition(|editor, _| editor.context_menu_visible())
14022        .await;
14023    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14024        editor
14025            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14026            .unwrap()
14027    });
14028    cx.assert_editor_state(expected);
14029    handle_resolve_completion_request(&mut cx, None).await;
14030    apply_additional_edits.await.unwrap();
14031
14032    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14033    let completion_text = "foo_and_bar";
14034    let initial_state = indoc! {"
14035        1. ooanbˇ
14036        2. zooanbˇ
14037        3. ooanbˇz
14038        4. zooanbˇz
14039        5. ooanˇ
14040        6. oanbˇ
14041
14042        ooanbˇ
14043    "};
14044    let completion_marked_buffer = indoc! {"
14045        1. ooanb
14046        2. zooanb
14047        3. ooanbz
14048        4. zooanbz
14049        5. ooan
14050        6. oanb
14051
14052        <ooanb|>
14053    "};
14054    let expected = indoc! {"
14055        1. foo_and_barˇ
14056        2. zfoo_and_barˇ
14057        3. foo_and_barˇz
14058        4. zfoo_and_barˇz
14059        5. ooanfoo_and_barˇ
14060        6. oanbfoo_and_barˇ
14061
14062        foo_and_barˇ
14063    "};
14064    cx.set_state(initial_state);
14065    cx.update_editor(|editor, window, cx| {
14066        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14067    });
14068    handle_completion_request_with_insert_and_replace(
14069        &mut cx,
14070        completion_marked_buffer,
14071        vec![(completion_text, completion_text)],
14072        Arc::new(AtomicUsize::new(0)),
14073    )
14074    .await;
14075    cx.condition(|editor, _| editor.context_menu_visible())
14076        .await;
14077    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14078        editor
14079            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14080            .unwrap()
14081    });
14082    cx.assert_editor_state(expected);
14083    handle_resolve_completion_request(&mut cx, None).await;
14084    apply_additional_edits.await.unwrap();
14085
14086    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14087    // (expects the same as if it was inserted at the end)
14088    let completion_text = "foo_and_bar";
14089    let initial_state = indoc! {"
14090        1. ooˇanb
14091        2. zooˇanb
14092        3. ooˇanbz
14093        4. zooˇanbz
14094
14095        ooˇanb
14096    "};
14097    let completion_marked_buffer = indoc! {"
14098        1. ooanb
14099        2. zooanb
14100        3. ooanbz
14101        4. zooanbz
14102
14103        <oo|anb>
14104    "};
14105    let expected = indoc! {"
14106        1. foo_and_barˇ
14107        2. zfoo_and_barˇ
14108        3. foo_and_barˇz
14109        4. zfoo_and_barˇz
14110
14111        foo_and_barˇ
14112    "};
14113    cx.set_state(initial_state);
14114    cx.update_editor(|editor, window, cx| {
14115        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14116    });
14117    handle_completion_request_with_insert_and_replace(
14118        &mut cx,
14119        completion_marked_buffer,
14120        vec![(completion_text, completion_text)],
14121        Arc::new(AtomicUsize::new(0)),
14122    )
14123    .await;
14124    cx.condition(|editor, _| editor.context_menu_visible())
14125        .await;
14126    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14127        editor
14128            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14129            .unwrap()
14130    });
14131    cx.assert_editor_state(expected);
14132    handle_resolve_completion_request(&mut cx, None).await;
14133    apply_additional_edits.await.unwrap();
14134}
14135
14136// This used to crash
14137#[gpui::test]
14138async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14139    init_test(cx, |_| {});
14140
14141    let buffer_text = indoc! {"
14142        fn main() {
14143            10.satu;
14144
14145            //
14146            // separate cursors so they open in different excerpts (manually reproducible)
14147            //
14148
14149            10.satu20;
14150        }
14151    "};
14152    let multibuffer_text_with_selections = indoc! {"
14153        fn main() {
14154            10.satuˇ;
14155
14156            //
14157
14158            //
14159
14160            10.satuˇ20;
14161        }
14162    "};
14163    let expected_multibuffer = indoc! {"
14164        fn main() {
14165            10.saturating_sub()ˇ;
14166
14167            //
14168
14169            //
14170
14171            10.saturating_sub()ˇ;
14172        }
14173    "};
14174
14175    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14176    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14177
14178    let fs = FakeFs::new(cx.executor());
14179    fs.insert_tree(
14180        path!("/a"),
14181        json!({
14182            "main.rs": buffer_text,
14183        }),
14184    )
14185    .await;
14186
14187    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14188    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14189    language_registry.add(rust_lang());
14190    let mut fake_servers = language_registry.register_fake_lsp(
14191        "Rust",
14192        FakeLspAdapter {
14193            capabilities: lsp::ServerCapabilities {
14194                completion_provider: Some(lsp::CompletionOptions {
14195                    resolve_provider: None,
14196                    ..lsp::CompletionOptions::default()
14197                }),
14198                ..lsp::ServerCapabilities::default()
14199            },
14200            ..FakeLspAdapter::default()
14201        },
14202    );
14203    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14204    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14205    let buffer = project
14206        .update(cx, |project, cx| {
14207            project.open_local_buffer(path!("/a/main.rs"), cx)
14208        })
14209        .await
14210        .unwrap();
14211
14212    let multi_buffer = cx.new(|cx| {
14213        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14214        multi_buffer.push_excerpts(
14215            buffer.clone(),
14216            [ExcerptRange::new(0..first_excerpt_end)],
14217            cx,
14218        );
14219        multi_buffer.push_excerpts(
14220            buffer.clone(),
14221            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14222            cx,
14223        );
14224        multi_buffer
14225    });
14226
14227    let editor = workspace
14228        .update(cx, |_, window, cx| {
14229            cx.new(|cx| {
14230                Editor::new(
14231                    EditorMode::Full {
14232                        scale_ui_elements_with_buffer_font_size: false,
14233                        show_active_line_background: false,
14234                        sizing_behavior: SizingBehavior::Default,
14235                    },
14236                    multi_buffer.clone(),
14237                    Some(project.clone()),
14238                    window,
14239                    cx,
14240                )
14241            })
14242        })
14243        .unwrap();
14244
14245    let pane = workspace
14246        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14247        .unwrap();
14248    pane.update_in(cx, |pane, window, cx| {
14249        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14250    });
14251
14252    let fake_server = fake_servers.next().await.unwrap();
14253
14254    editor.update_in(cx, |editor, window, cx| {
14255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14256            s.select_ranges([
14257                Point::new(1, 11)..Point::new(1, 11),
14258                Point::new(7, 11)..Point::new(7, 11),
14259            ])
14260        });
14261
14262        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14263    });
14264
14265    editor.update_in(cx, |editor, window, cx| {
14266        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14267    });
14268
14269    fake_server
14270        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14271            let completion_item = lsp::CompletionItem {
14272                label: "saturating_sub()".into(),
14273                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14274                    lsp::InsertReplaceEdit {
14275                        new_text: "saturating_sub()".to_owned(),
14276                        insert: lsp::Range::new(
14277                            lsp::Position::new(7, 7),
14278                            lsp::Position::new(7, 11),
14279                        ),
14280                        replace: lsp::Range::new(
14281                            lsp::Position::new(7, 7),
14282                            lsp::Position::new(7, 13),
14283                        ),
14284                    },
14285                )),
14286                ..lsp::CompletionItem::default()
14287            };
14288
14289            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14290        })
14291        .next()
14292        .await
14293        .unwrap();
14294
14295    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14296        .await;
14297
14298    editor
14299        .update_in(cx, |editor, window, cx| {
14300            editor
14301                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14302                .unwrap()
14303        })
14304        .await
14305        .unwrap();
14306
14307    editor.update(cx, |editor, cx| {
14308        assert_text_with_selections(editor, expected_multibuffer, cx);
14309    })
14310}
14311
14312#[gpui::test]
14313async fn test_completion(cx: &mut TestAppContext) {
14314    init_test(cx, |_| {});
14315
14316    let mut cx = EditorLspTestContext::new_rust(
14317        lsp::ServerCapabilities {
14318            completion_provider: Some(lsp::CompletionOptions {
14319                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14320                resolve_provider: Some(true),
14321                ..Default::default()
14322            }),
14323            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14324            ..Default::default()
14325        },
14326        cx,
14327    )
14328    .await;
14329    let counter = Arc::new(AtomicUsize::new(0));
14330
14331    cx.set_state(indoc! {"
14332        oneˇ
14333        two
14334        three
14335    "});
14336    cx.simulate_keystroke(".");
14337    handle_completion_request(
14338        indoc! {"
14339            one.|<>
14340            two
14341            three
14342        "},
14343        vec!["first_completion", "second_completion"],
14344        true,
14345        counter.clone(),
14346        &mut cx,
14347    )
14348    .await;
14349    cx.condition(|editor, _| editor.context_menu_visible())
14350        .await;
14351    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14352
14353    let _handler = handle_signature_help_request(
14354        &mut cx,
14355        lsp::SignatureHelp {
14356            signatures: vec![lsp::SignatureInformation {
14357                label: "test signature".to_string(),
14358                documentation: None,
14359                parameters: Some(vec![lsp::ParameterInformation {
14360                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14361                    documentation: None,
14362                }]),
14363                active_parameter: None,
14364            }],
14365            active_signature: None,
14366            active_parameter: None,
14367        },
14368    );
14369    cx.update_editor(|editor, window, cx| {
14370        assert!(
14371            !editor.signature_help_state.is_shown(),
14372            "No signature help was called for"
14373        );
14374        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14375    });
14376    cx.run_until_parked();
14377    cx.update_editor(|editor, _, _| {
14378        assert!(
14379            !editor.signature_help_state.is_shown(),
14380            "No signature help should be shown when completions menu is open"
14381        );
14382    });
14383
14384    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14385        editor.context_menu_next(&Default::default(), window, cx);
14386        editor
14387            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14388            .unwrap()
14389    });
14390    cx.assert_editor_state(indoc! {"
14391        one.second_completionˇ
14392        two
14393        three
14394    "});
14395
14396    handle_resolve_completion_request(
14397        &mut cx,
14398        Some(vec![
14399            (
14400                //This overlaps with the primary completion edit which is
14401                //misbehavior from the LSP spec, test that we filter it out
14402                indoc! {"
14403                    one.second_ˇcompletion
14404                    two
14405                    threeˇ
14406                "},
14407                "overlapping additional edit",
14408            ),
14409            (
14410                indoc! {"
14411                    one.second_completion
14412                    two
14413                    threeˇ
14414                "},
14415                "\nadditional edit",
14416            ),
14417        ]),
14418    )
14419    .await;
14420    apply_additional_edits.await.unwrap();
14421    cx.assert_editor_state(indoc! {"
14422        one.second_completionˇ
14423        two
14424        three
14425        additional edit
14426    "});
14427
14428    cx.set_state(indoc! {"
14429        one.second_completion
14430        twoˇ
14431        threeˇ
14432        additional edit
14433    "});
14434    cx.simulate_keystroke(" ");
14435    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14436    cx.simulate_keystroke("s");
14437    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14438
14439    cx.assert_editor_state(indoc! {"
14440        one.second_completion
14441        two sˇ
14442        three sˇ
14443        additional edit
14444    "});
14445    handle_completion_request(
14446        indoc! {"
14447            one.second_completion
14448            two s
14449            three <s|>
14450            additional edit
14451        "},
14452        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14453        true,
14454        counter.clone(),
14455        &mut cx,
14456    )
14457    .await;
14458    cx.condition(|editor, _| editor.context_menu_visible())
14459        .await;
14460    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14461
14462    cx.simulate_keystroke("i");
14463
14464    handle_completion_request(
14465        indoc! {"
14466            one.second_completion
14467            two si
14468            three <si|>
14469            additional edit
14470        "},
14471        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14472        true,
14473        counter.clone(),
14474        &mut cx,
14475    )
14476    .await;
14477    cx.condition(|editor, _| editor.context_menu_visible())
14478        .await;
14479    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14480
14481    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14482        editor
14483            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14484            .unwrap()
14485    });
14486    cx.assert_editor_state(indoc! {"
14487        one.second_completion
14488        two sixth_completionˇ
14489        three sixth_completionˇ
14490        additional edit
14491    "});
14492
14493    apply_additional_edits.await.unwrap();
14494
14495    update_test_language_settings(&mut cx, |settings| {
14496        settings.defaults.show_completions_on_input = Some(false);
14497    });
14498    cx.set_state("editorˇ");
14499    cx.simulate_keystroke(".");
14500    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14501    cx.simulate_keystrokes("c l o");
14502    cx.assert_editor_state("editor.cloˇ");
14503    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14504    cx.update_editor(|editor, window, cx| {
14505        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14506    });
14507    handle_completion_request(
14508        "editor.<clo|>",
14509        vec!["close", "clobber"],
14510        true,
14511        counter.clone(),
14512        &mut cx,
14513    )
14514    .await;
14515    cx.condition(|editor, _| editor.context_menu_visible())
14516        .await;
14517    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14518
14519    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14520        editor
14521            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14522            .unwrap()
14523    });
14524    cx.assert_editor_state("editor.clobberˇ");
14525    handle_resolve_completion_request(&mut cx, None).await;
14526    apply_additional_edits.await.unwrap();
14527}
14528
14529#[gpui::test]
14530async fn test_completion_reuse(cx: &mut TestAppContext) {
14531    init_test(cx, |_| {});
14532
14533    let mut cx = EditorLspTestContext::new_rust(
14534        lsp::ServerCapabilities {
14535            completion_provider: Some(lsp::CompletionOptions {
14536                trigger_characters: Some(vec![".".to_string()]),
14537                ..Default::default()
14538            }),
14539            ..Default::default()
14540        },
14541        cx,
14542    )
14543    .await;
14544
14545    let counter = Arc::new(AtomicUsize::new(0));
14546    cx.set_state("objˇ");
14547    cx.simulate_keystroke(".");
14548
14549    // Initial completion request returns complete results
14550    let is_incomplete = false;
14551    handle_completion_request(
14552        "obj.|<>",
14553        vec!["a", "ab", "abc"],
14554        is_incomplete,
14555        counter.clone(),
14556        &mut cx,
14557    )
14558    .await;
14559    cx.run_until_parked();
14560    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14561    cx.assert_editor_state("obj.ˇ");
14562    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14563
14564    // Type "a" - filters existing completions
14565    cx.simulate_keystroke("a");
14566    cx.run_until_parked();
14567    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14568    cx.assert_editor_state("obj.aˇ");
14569    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14570
14571    // Type "b" - filters existing completions
14572    cx.simulate_keystroke("b");
14573    cx.run_until_parked();
14574    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14575    cx.assert_editor_state("obj.abˇ");
14576    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14577
14578    // Type "c" - filters existing completions
14579    cx.simulate_keystroke("c");
14580    cx.run_until_parked();
14581    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14582    cx.assert_editor_state("obj.abcˇ");
14583    check_displayed_completions(vec!["abc"], &mut cx);
14584
14585    // Backspace to delete "c" - filters existing completions
14586    cx.update_editor(|editor, window, cx| {
14587        editor.backspace(&Backspace, window, cx);
14588    });
14589    cx.run_until_parked();
14590    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14591    cx.assert_editor_state("obj.abˇ");
14592    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14593
14594    // Moving cursor to the left dismisses menu.
14595    cx.update_editor(|editor, window, cx| {
14596        editor.move_left(&MoveLeft, window, cx);
14597    });
14598    cx.run_until_parked();
14599    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14600    cx.assert_editor_state("obj.aˇb");
14601    cx.update_editor(|editor, _, _| {
14602        assert_eq!(editor.context_menu_visible(), false);
14603    });
14604
14605    // Type "b" - new request
14606    cx.simulate_keystroke("b");
14607    let is_incomplete = false;
14608    handle_completion_request(
14609        "obj.<ab|>a",
14610        vec!["ab", "abc"],
14611        is_incomplete,
14612        counter.clone(),
14613        &mut cx,
14614    )
14615    .await;
14616    cx.run_until_parked();
14617    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14618    cx.assert_editor_state("obj.abˇb");
14619    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14620
14621    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14622    cx.update_editor(|editor, window, cx| {
14623        editor.backspace(&Backspace, window, cx);
14624    });
14625    let is_incomplete = false;
14626    handle_completion_request(
14627        "obj.<a|>b",
14628        vec!["a", "ab", "abc"],
14629        is_incomplete,
14630        counter.clone(),
14631        &mut cx,
14632    )
14633    .await;
14634    cx.run_until_parked();
14635    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14636    cx.assert_editor_state("obj.aˇb");
14637    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14638
14639    // Backspace to delete "a" - dismisses menu.
14640    cx.update_editor(|editor, window, cx| {
14641        editor.backspace(&Backspace, window, cx);
14642    });
14643    cx.run_until_parked();
14644    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14645    cx.assert_editor_state("obj.ˇb");
14646    cx.update_editor(|editor, _, _| {
14647        assert_eq!(editor.context_menu_visible(), false);
14648    });
14649}
14650
14651#[gpui::test]
14652async fn test_word_completion(cx: &mut TestAppContext) {
14653    let lsp_fetch_timeout_ms = 10;
14654    init_test(cx, |language_settings| {
14655        language_settings.defaults.completions = Some(CompletionSettingsContent {
14656            words_min_length: Some(0),
14657            lsp_fetch_timeout_ms: Some(10),
14658            lsp_insert_mode: Some(LspInsertMode::Insert),
14659            ..Default::default()
14660        });
14661    });
14662
14663    let mut cx = EditorLspTestContext::new_rust(
14664        lsp::ServerCapabilities {
14665            completion_provider: Some(lsp::CompletionOptions {
14666                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14667                ..lsp::CompletionOptions::default()
14668            }),
14669            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14670            ..lsp::ServerCapabilities::default()
14671        },
14672        cx,
14673    )
14674    .await;
14675
14676    let throttle_completions = Arc::new(AtomicBool::new(false));
14677
14678    let lsp_throttle_completions = throttle_completions.clone();
14679    let _completion_requests_handler =
14680        cx.lsp
14681            .server
14682            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14683                let lsp_throttle_completions = lsp_throttle_completions.clone();
14684                let cx = cx.clone();
14685                async move {
14686                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14687                        cx.background_executor()
14688                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14689                            .await;
14690                    }
14691                    Ok(Some(lsp::CompletionResponse::Array(vec![
14692                        lsp::CompletionItem {
14693                            label: "first".into(),
14694                            ..lsp::CompletionItem::default()
14695                        },
14696                        lsp::CompletionItem {
14697                            label: "last".into(),
14698                            ..lsp::CompletionItem::default()
14699                        },
14700                    ])))
14701                }
14702            });
14703
14704    cx.set_state(indoc! {"
14705        oneˇ
14706        two
14707        three
14708    "});
14709    cx.simulate_keystroke(".");
14710    cx.executor().run_until_parked();
14711    cx.condition(|editor, _| editor.context_menu_visible())
14712        .await;
14713    cx.update_editor(|editor, window, cx| {
14714        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14715        {
14716            assert_eq!(
14717                completion_menu_entries(menu),
14718                &["first", "last"],
14719                "When LSP server is fast to reply, no fallback word completions are used"
14720            );
14721        } else {
14722            panic!("expected completion menu to be open");
14723        }
14724        editor.cancel(&Cancel, window, cx);
14725    });
14726    cx.executor().run_until_parked();
14727    cx.condition(|editor, _| !editor.context_menu_visible())
14728        .await;
14729
14730    throttle_completions.store(true, atomic::Ordering::Release);
14731    cx.simulate_keystroke(".");
14732    cx.executor()
14733        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14734    cx.executor().run_until_parked();
14735    cx.condition(|editor, _| editor.context_menu_visible())
14736        .await;
14737    cx.update_editor(|editor, _, _| {
14738        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14739        {
14740            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14741                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14742        } else {
14743            panic!("expected completion menu to be open");
14744        }
14745    });
14746}
14747
14748#[gpui::test]
14749async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14750    init_test(cx, |language_settings| {
14751        language_settings.defaults.completions = Some(CompletionSettingsContent {
14752            words: Some(WordsCompletionMode::Enabled),
14753            words_min_length: Some(0),
14754            lsp_insert_mode: Some(LspInsertMode::Insert),
14755            ..Default::default()
14756        });
14757    });
14758
14759    let mut cx = EditorLspTestContext::new_rust(
14760        lsp::ServerCapabilities {
14761            completion_provider: Some(lsp::CompletionOptions {
14762                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14763                ..lsp::CompletionOptions::default()
14764            }),
14765            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14766            ..lsp::ServerCapabilities::default()
14767        },
14768        cx,
14769    )
14770    .await;
14771
14772    let _completion_requests_handler =
14773        cx.lsp
14774            .server
14775            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14776                Ok(Some(lsp::CompletionResponse::Array(vec![
14777                    lsp::CompletionItem {
14778                        label: "first".into(),
14779                        ..lsp::CompletionItem::default()
14780                    },
14781                    lsp::CompletionItem {
14782                        label: "last".into(),
14783                        ..lsp::CompletionItem::default()
14784                    },
14785                ])))
14786            });
14787
14788    cx.set_state(indoc! {"ˇ
14789        first
14790        last
14791        second
14792    "});
14793    cx.simulate_keystroke(".");
14794    cx.executor().run_until_parked();
14795    cx.condition(|editor, _| editor.context_menu_visible())
14796        .await;
14797    cx.update_editor(|editor, _, _| {
14798        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14799        {
14800            assert_eq!(
14801                completion_menu_entries(menu),
14802                &["first", "last", "second"],
14803                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14804            );
14805        } else {
14806            panic!("expected completion menu to be open");
14807        }
14808    });
14809}
14810
14811#[gpui::test]
14812async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14813    init_test(cx, |language_settings| {
14814        language_settings.defaults.completions = Some(CompletionSettingsContent {
14815            words: Some(WordsCompletionMode::Disabled),
14816            words_min_length: Some(0),
14817            lsp_insert_mode: Some(LspInsertMode::Insert),
14818            ..Default::default()
14819        });
14820    });
14821
14822    let mut cx = EditorLspTestContext::new_rust(
14823        lsp::ServerCapabilities {
14824            completion_provider: Some(lsp::CompletionOptions {
14825                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14826                ..lsp::CompletionOptions::default()
14827            }),
14828            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14829            ..lsp::ServerCapabilities::default()
14830        },
14831        cx,
14832    )
14833    .await;
14834
14835    let _completion_requests_handler =
14836        cx.lsp
14837            .server
14838            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14839                panic!("LSP completions should not be queried when dealing with word completions")
14840            });
14841
14842    cx.set_state(indoc! {"ˇ
14843        first
14844        last
14845        second
14846    "});
14847    cx.update_editor(|editor, window, cx| {
14848        editor.show_word_completions(&ShowWordCompletions, window, cx);
14849    });
14850    cx.executor().run_until_parked();
14851    cx.condition(|editor, _| editor.context_menu_visible())
14852        .await;
14853    cx.update_editor(|editor, _, _| {
14854        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14855        {
14856            assert_eq!(
14857                completion_menu_entries(menu),
14858                &["first", "last", "second"],
14859                "`ShowWordCompletions` action should show word completions"
14860            );
14861        } else {
14862            panic!("expected completion menu to be open");
14863        }
14864    });
14865
14866    cx.simulate_keystroke("l");
14867    cx.executor().run_until_parked();
14868    cx.condition(|editor, _| editor.context_menu_visible())
14869        .await;
14870    cx.update_editor(|editor, _, _| {
14871        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14872        {
14873            assert_eq!(
14874                completion_menu_entries(menu),
14875                &["last"],
14876                "After showing word completions, further editing should filter them and not query the LSP"
14877            );
14878        } else {
14879            panic!("expected completion menu to be open");
14880        }
14881    });
14882}
14883
14884#[gpui::test]
14885async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14886    init_test(cx, |language_settings| {
14887        language_settings.defaults.completions = Some(CompletionSettingsContent {
14888            words_min_length: Some(0),
14889            lsp: Some(false),
14890            lsp_insert_mode: Some(LspInsertMode::Insert),
14891            ..Default::default()
14892        });
14893    });
14894
14895    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14896
14897    cx.set_state(indoc! {"ˇ
14898        0_usize
14899        let
14900        33
14901        4.5f32
14902    "});
14903    cx.update_editor(|editor, window, cx| {
14904        editor.show_completions(&ShowCompletions::default(), window, cx);
14905    });
14906    cx.executor().run_until_parked();
14907    cx.condition(|editor, _| editor.context_menu_visible())
14908        .await;
14909    cx.update_editor(|editor, window, cx| {
14910        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14911        {
14912            assert_eq!(
14913                completion_menu_entries(menu),
14914                &["let"],
14915                "With no digits in the completion query, no digits should be in the word completions"
14916            );
14917        } else {
14918            panic!("expected completion menu to be open");
14919        }
14920        editor.cancel(&Cancel, window, cx);
14921    });
14922
14923    cx.set_state(indoc! {"14924        0_usize
14925        let
14926        3
14927        33.35f32
14928    "});
14929    cx.update_editor(|editor, window, cx| {
14930        editor.show_completions(&ShowCompletions::default(), window, cx);
14931    });
14932    cx.executor().run_until_parked();
14933    cx.condition(|editor, _| editor.context_menu_visible())
14934        .await;
14935    cx.update_editor(|editor, _, _| {
14936        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14937        {
14938            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14939                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14940        } else {
14941            panic!("expected completion menu to be open");
14942        }
14943    });
14944}
14945
14946#[gpui::test]
14947async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14948    init_test(cx, |language_settings| {
14949        language_settings.defaults.completions = Some(CompletionSettingsContent {
14950            words: Some(WordsCompletionMode::Enabled),
14951            words_min_length: Some(3),
14952            lsp_insert_mode: Some(LspInsertMode::Insert),
14953            ..Default::default()
14954        });
14955    });
14956
14957    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14958    cx.set_state(indoc! {"ˇ
14959        wow
14960        wowen
14961        wowser
14962    "});
14963    cx.simulate_keystroke("w");
14964    cx.executor().run_until_parked();
14965    cx.update_editor(|editor, _, _| {
14966        if editor.context_menu.borrow_mut().is_some() {
14967            panic!(
14968                "expected completion menu to be hidden, as words completion threshold is not met"
14969            );
14970        }
14971    });
14972
14973    cx.update_editor(|editor, window, cx| {
14974        editor.show_word_completions(&ShowWordCompletions, window, cx);
14975    });
14976    cx.executor().run_until_parked();
14977    cx.update_editor(|editor, window, cx| {
14978        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14979        {
14980            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");
14981        } else {
14982            panic!("expected completion menu to be open after the word completions are called with an action");
14983        }
14984
14985        editor.cancel(&Cancel, window, cx);
14986    });
14987    cx.update_editor(|editor, _, _| {
14988        if editor.context_menu.borrow_mut().is_some() {
14989            panic!("expected completion menu to be hidden after canceling");
14990        }
14991    });
14992
14993    cx.simulate_keystroke("o");
14994    cx.executor().run_until_parked();
14995    cx.update_editor(|editor, _, _| {
14996        if editor.context_menu.borrow_mut().is_some() {
14997            panic!(
14998                "expected completion menu to be hidden, as words completion threshold is not met still"
14999            );
15000        }
15001    });
15002
15003    cx.simulate_keystroke("w");
15004    cx.executor().run_until_parked();
15005    cx.update_editor(|editor, _, _| {
15006        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15007        {
15008            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15009        } else {
15010            panic!("expected completion menu to be open after the word completions threshold is met");
15011        }
15012    });
15013}
15014
15015#[gpui::test]
15016async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15017    init_test(cx, |language_settings| {
15018        language_settings.defaults.completions = Some(CompletionSettingsContent {
15019            words: Some(WordsCompletionMode::Enabled),
15020            words_min_length: Some(0),
15021            lsp_insert_mode: Some(LspInsertMode::Insert),
15022            ..Default::default()
15023        });
15024    });
15025
15026    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15027    cx.update_editor(|editor, _, _| {
15028        editor.disable_word_completions();
15029    });
15030    cx.set_state(indoc! {"ˇ
15031        wow
15032        wowen
15033        wowser
15034    "});
15035    cx.simulate_keystroke("w");
15036    cx.executor().run_until_parked();
15037    cx.update_editor(|editor, _, _| {
15038        if editor.context_menu.borrow_mut().is_some() {
15039            panic!(
15040                "expected completion menu to be hidden, as words completion are disabled for this editor"
15041            );
15042        }
15043    });
15044
15045    cx.update_editor(|editor, window, cx| {
15046        editor.show_word_completions(&ShowWordCompletions, window, cx);
15047    });
15048    cx.executor().run_until_parked();
15049    cx.update_editor(|editor, _, _| {
15050        if editor.context_menu.borrow_mut().is_some() {
15051            panic!(
15052                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15053            );
15054        }
15055    });
15056}
15057
15058fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15059    let position = || lsp::Position {
15060        line: params.text_document_position.position.line,
15061        character: params.text_document_position.position.character,
15062    };
15063    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15064        range: lsp::Range {
15065            start: position(),
15066            end: position(),
15067        },
15068        new_text: text.to_string(),
15069    }))
15070}
15071
15072#[gpui::test]
15073async fn test_multiline_completion(cx: &mut TestAppContext) {
15074    init_test(cx, |_| {});
15075
15076    let fs = FakeFs::new(cx.executor());
15077    fs.insert_tree(
15078        path!("/a"),
15079        json!({
15080            "main.ts": "a",
15081        }),
15082    )
15083    .await;
15084
15085    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15086    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15087    let typescript_language = Arc::new(Language::new(
15088        LanguageConfig {
15089            name: "TypeScript".into(),
15090            matcher: LanguageMatcher {
15091                path_suffixes: vec!["ts".to_string()],
15092                ..LanguageMatcher::default()
15093            },
15094            line_comments: vec!["// ".into()],
15095            ..LanguageConfig::default()
15096        },
15097        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15098    ));
15099    language_registry.add(typescript_language.clone());
15100    let mut fake_servers = language_registry.register_fake_lsp(
15101        "TypeScript",
15102        FakeLspAdapter {
15103            capabilities: lsp::ServerCapabilities {
15104                completion_provider: Some(lsp::CompletionOptions {
15105                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15106                    ..lsp::CompletionOptions::default()
15107                }),
15108                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15109                ..lsp::ServerCapabilities::default()
15110            },
15111            // Emulate vtsls label generation
15112            label_for_completion: Some(Box::new(|item, _| {
15113                let text = if let Some(description) = item
15114                    .label_details
15115                    .as_ref()
15116                    .and_then(|label_details| label_details.description.as_ref())
15117                {
15118                    format!("{} {}", item.label, description)
15119                } else if let Some(detail) = &item.detail {
15120                    format!("{} {}", item.label, detail)
15121                } else {
15122                    item.label.clone()
15123                };
15124                Some(language::CodeLabel::plain(text, None))
15125            })),
15126            ..FakeLspAdapter::default()
15127        },
15128    );
15129    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15130    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15131    let worktree_id = workspace
15132        .update(cx, |workspace, _window, cx| {
15133            workspace.project().update(cx, |project, cx| {
15134                project.worktrees(cx).next().unwrap().read(cx).id()
15135            })
15136        })
15137        .unwrap();
15138    let _buffer = project
15139        .update(cx, |project, cx| {
15140            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15141        })
15142        .await
15143        .unwrap();
15144    let editor = workspace
15145        .update(cx, |workspace, window, cx| {
15146            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15147        })
15148        .unwrap()
15149        .await
15150        .unwrap()
15151        .downcast::<Editor>()
15152        .unwrap();
15153    let fake_server = fake_servers.next().await.unwrap();
15154
15155    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15156    let multiline_label_2 = "a\nb\nc\n";
15157    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15158    let multiline_description = "d\ne\nf\n";
15159    let multiline_detail_2 = "g\nh\ni\n";
15160
15161    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15162        move |params, _| async move {
15163            Ok(Some(lsp::CompletionResponse::Array(vec![
15164                lsp::CompletionItem {
15165                    label: multiline_label.to_string(),
15166                    text_edit: gen_text_edit(&params, "new_text_1"),
15167                    ..lsp::CompletionItem::default()
15168                },
15169                lsp::CompletionItem {
15170                    label: "single line label 1".to_string(),
15171                    detail: Some(multiline_detail.to_string()),
15172                    text_edit: gen_text_edit(&params, "new_text_2"),
15173                    ..lsp::CompletionItem::default()
15174                },
15175                lsp::CompletionItem {
15176                    label: "single line label 2".to_string(),
15177                    label_details: Some(lsp::CompletionItemLabelDetails {
15178                        description: Some(multiline_description.to_string()),
15179                        detail: None,
15180                    }),
15181                    text_edit: gen_text_edit(&params, "new_text_2"),
15182                    ..lsp::CompletionItem::default()
15183                },
15184                lsp::CompletionItem {
15185                    label: multiline_label_2.to_string(),
15186                    detail: Some(multiline_detail_2.to_string()),
15187                    text_edit: gen_text_edit(&params, "new_text_3"),
15188                    ..lsp::CompletionItem::default()
15189                },
15190                lsp::CompletionItem {
15191                    label: "Label with many     spaces and \t but without newlines".to_string(),
15192                    detail: Some(
15193                        "Details with many     spaces and \t but without newlines".to_string(),
15194                    ),
15195                    text_edit: gen_text_edit(&params, "new_text_4"),
15196                    ..lsp::CompletionItem::default()
15197                },
15198            ])))
15199        },
15200    );
15201
15202    editor.update_in(cx, |editor, window, cx| {
15203        cx.focus_self(window);
15204        editor.move_to_end(&MoveToEnd, window, cx);
15205        editor.handle_input(".", window, cx);
15206    });
15207    cx.run_until_parked();
15208    completion_handle.next().await.unwrap();
15209
15210    editor.update(cx, |editor, _| {
15211        assert!(editor.context_menu_visible());
15212        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15213        {
15214            let completion_labels = menu
15215                .completions
15216                .borrow()
15217                .iter()
15218                .map(|c| c.label.text.clone())
15219                .collect::<Vec<_>>();
15220            assert_eq!(
15221                completion_labels,
15222                &[
15223                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15224                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15225                    "single line label 2 d e f ",
15226                    "a b c g h i ",
15227                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15228                ],
15229                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15230            );
15231
15232            for completion in menu
15233                .completions
15234                .borrow()
15235                .iter() {
15236                    assert_eq!(
15237                        completion.label.filter_range,
15238                        0..completion.label.text.len(),
15239                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15240                    );
15241                }
15242        } else {
15243            panic!("expected completion menu to be open");
15244        }
15245    });
15246}
15247
15248#[gpui::test]
15249async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15250    init_test(cx, |_| {});
15251    let mut cx = EditorLspTestContext::new_rust(
15252        lsp::ServerCapabilities {
15253            completion_provider: Some(lsp::CompletionOptions {
15254                trigger_characters: Some(vec![".".to_string()]),
15255                ..Default::default()
15256            }),
15257            ..Default::default()
15258        },
15259        cx,
15260    )
15261    .await;
15262    cx.lsp
15263        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15264            Ok(Some(lsp::CompletionResponse::Array(vec![
15265                lsp::CompletionItem {
15266                    label: "first".into(),
15267                    ..Default::default()
15268                },
15269                lsp::CompletionItem {
15270                    label: "last".into(),
15271                    ..Default::default()
15272                },
15273            ])))
15274        });
15275    cx.set_state("variableˇ");
15276    cx.simulate_keystroke(".");
15277    cx.executor().run_until_parked();
15278
15279    cx.update_editor(|editor, _, _| {
15280        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15281        {
15282            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15283        } else {
15284            panic!("expected completion menu to be open");
15285        }
15286    });
15287
15288    cx.update_editor(|editor, window, cx| {
15289        editor.move_page_down(&MovePageDown::default(), window, cx);
15290        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15291        {
15292            assert!(
15293                menu.selected_item == 1,
15294                "expected PageDown to select the last item from the context menu"
15295            );
15296        } else {
15297            panic!("expected completion menu to stay open after PageDown");
15298        }
15299    });
15300
15301    cx.update_editor(|editor, window, cx| {
15302        editor.move_page_up(&MovePageUp::default(), window, cx);
15303        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15304        {
15305            assert!(
15306                menu.selected_item == 0,
15307                "expected PageUp to select the first item from the context menu"
15308            );
15309        } else {
15310            panic!("expected completion menu to stay open after PageUp");
15311        }
15312    });
15313}
15314
15315#[gpui::test]
15316async fn test_as_is_completions(cx: &mut TestAppContext) {
15317    init_test(cx, |_| {});
15318    let mut cx = EditorLspTestContext::new_rust(
15319        lsp::ServerCapabilities {
15320            completion_provider: Some(lsp::CompletionOptions {
15321                ..Default::default()
15322            }),
15323            ..Default::default()
15324        },
15325        cx,
15326    )
15327    .await;
15328    cx.lsp
15329        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15330            Ok(Some(lsp::CompletionResponse::Array(vec![
15331                lsp::CompletionItem {
15332                    label: "unsafe".into(),
15333                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15334                        range: lsp::Range {
15335                            start: lsp::Position {
15336                                line: 1,
15337                                character: 2,
15338                            },
15339                            end: lsp::Position {
15340                                line: 1,
15341                                character: 3,
15342                            },
15343                        },
15344                        new_text: "unsafe".to_string(),
15345                    })),
15346                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15347                    ..Default::default()
15348                },
15349            ])))
15350        });
15351    cx.set_state("fn a() {}\n");
15352    cx.executor().run_until_parked();
15353    cx.update_editor(|editor, window, cx| {
15354        editor.show_completions(
15355            &ShowCompletions {
15356                trigger: Some("\n".into()),
15357            },
15358            window,
15359            cx,
15360        );
15361    });
15362    cx.executor().run_until_parked();
15363
15364    cx.update_editor(|editor, window, cx| {
15365        editor.confirm_completion(&Default::default(), window, cx)
15366    });
15367    cx.executor().run_until_parked();
15368    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15369}
15370
15371#[gpui::test]
15372async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15373    init_test(cx, |_| {});
15374    let language =
15375        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15376    let mut cx = EditorLspTestContext::new(
15377        language,
15378        lsp::ServerCapabilities {
15379            completion_provider: Some(lsp::CompletionOptions {
15380                ..lsp::CompletionOptions::default()
15381            }),
15382            ..lsp::ServerCapabilities::default()
15383        },
15384        cx,
15385    )
15386    .await;
15387
15388    cx.set_state(
15389        "#ifndef BAR_H
15390#define BAR_H
15391
15392#include <stdbool.h>
15393
15394int fn_branch(bool do_branch1, bool do_branch2);
15395
15396#endif // BAR_H
15397ˇ",
15398    );
15399    cx.executor().run_until_parked();
15400    cx.update_editor(|editor, window, cx| {
15401        editor.handle_input("#", window, cx);
15402    });
15403    cx.executor().run_until_parked();
15404    cx.update_editor(|editor, window, cx| {
15405        editor.handle_input("i", window, cx);
15406    });
15407    cx.executor().run_until_parked();
15408    cx.update_editor(|editor, window, cx| {
15409        editor.handle_input("n", window, cx);
15410    });
15411    cx.executor().run_until_parked();
15412    cx.assert_editor_state(
15413        "#ifndef BAR_H
15414#define BAR_H
15415
15416#include <stdbool.h>
15417
15418int fn_branch(bool do_branch1, bool do_branch2);
15419
15420#endif // BAR_H
15421#inˇ",
15422    );
15423
15424    cx.lsp
15425        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15426            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15427                is_incomplete: false,
15428                item_defaults: None,
15429                items: vec![lsp::CompletionItem {
15430                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15431                    label_details: Some(lsp::CompletionItemLabelDetails {
15432                        detail: Some("header".to_string()),
15433                        description: None,
15434                    }),
15435                    label: " include".to_string(),
15436                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15437                        range: lsp::Range {
15438                            start: lsp::Position {
15439                                line: 8,
15440                                character: 1,
15441                            },
15442                            end: lsp::Position {
15443                                line: 8,
15444                                character: 1,
15445                            },
15446                        },
15447                        new_text: "include \"$0\"".to_string(),
15448                    })),
15449                    sort_text: Some("40b67681include".to_string()),
15450                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15451                    filter_text: Some("include".to_string()),
15452                    insert_text: Some("include \"$0\"".to_string()),
15453                    ..lsp::CompletionItem::default()
15454                }],
15455            })))
15456        });
15457    cx.update_editor(|editor, window, cx| {
15458        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15459    });
15460    cx.executor().run_until_parked();
15461    cx.update_editor(|editor, window, cx| {
15462        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15463    });
15464    cx.executor().run_until_parked();
15465    cx.assert_editor_state(
15466        "#ifndef BAR_H
15467#define BAR_H
15468
15469#include <stdbool.h>
15470
15471int fn_branch(bool do_branch1, bool do_branch2);
15472
15473#endif // BAR_H
15474#include \"ˇ\"",
15475    );
15476
15477    cx.lsp
15478        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15479            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15480                is_incomplete: true,
15481                item_defaults: None,
15482                items: vec![lsp::CompletionItem {
15483                    kind: Some(lsp::CompletionItemKind::FILE),
15484                    label: "AGL/".to_string(),
15485                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15486                        range: lsp::Range {
15487                            start: lsp::Position {
15488                                line: 8,
15489                                character: 10,
15490                            },
15491                            end: lsp::Position {
15492                                line: 8,
15493                                character: 11,
15494                            },
15495                        },
15496                        new_text: "AGL/".to_string(),
15497                    })),
15498                    sort_text: Some("40b67681AGL/".to_string()),
15499                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15500                    filter_text: Some("AGL/".to_string()),
15501                    insert_text: Some("AGL/".to_string()),
15502                    ..lsp::CompletionItem::default()
15503                }],
15504            })))
15505        });
15506    cx.update_editor(|editor, window, cx| {
15507        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15508    });
15509    cx.executor().run_until_parked();
15510    cx.update_editor(|editor, window, cx| {
15511        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15512    });
15513    cx.executor().run_until_parked();
15514    cx.assert_editor_state(
15515        r##"#ifndef BAR_H
15516#define BAR_H
15517
15518#include <stdbool.h>
15519
15520int fn_branch(bool do_branch1, bool do_branch2);
15521
15522#endif // BAR_H
15523#include "AGL/ˇ"##,
15524    );
15525
15526    cx.update_editor(|editor, window, cx| {
15527        editor.handle_input("\"", window, cx);
15528    });
15529    cx.executor().run_until_parked();
15530    cx.assert_editor_state(
15531        r##"#ifndef BAR_H
15532#define BAR_H
15533
15534#include <stdbool.h>
15535
15536int fn_branch(bool do_branch1, bool do_branch2);
15537
15538#endif // BAR_H
15539#include "AGL/"ˇ"##,
15540    );
15541}
15542
15543#[gpui::test]
15544async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15545    init_test(cx, |_| {});
15546
15547    let mut cx = EditorLspTestContext::new_rust(
15548        lsp::ServerCapabilities {
15549            completion_provider: Some(lsp::CompletionOptions {
15550                trigger_characters: Some(vec![".".to_string()]),
15551                resolve_provider: Some(true),
15552                ..Default::default()
15553            }),
15554            ..Default::default()
15555        },
15556        cx,
15557    )
15558    .await;
15559
15560    cx.set_state("fn main() { let a = 2ˇ; }");
15561    cx.simulate_keystroke(".");
15562    let completion_item = lsp::CompletionItem {
15563        label: "Some".into(),
15564        kind: Some(lsp::CompletionItemKind::SNIPPET),
15565        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15566        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15567            kind: lsp::MarkupKind::Markdown,
15568            value: "```rust\nSome(2)\n```".to_string(),
15569        })),
15570        deprecated: Some(false),
15571        sort_text: Some("Some".to_string()),
15572        filter_text: Some("Some".to_string()),
15573        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15574        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15575            range: lsp::Range {
15576                start: lsp::Position {
15577                    line: 0,
15578                    character: 22,
15579                },
15580                end: lsp::Position {
15581                    line: 0,
15582                    character: 22,
15583                },
15584            },
15585            new_text: "Some(2)".to_string(),
15586        })),
15587        additional_text_edits: Some(vec![lsp::TextEdit {
15588            range: lsp::Range {
15589                start: lsp::Position {
15590                    line: 0,
15591                    character: 20,
15592                },
15593                end: lsp::Position {
15594                    line: 0,
15595                    character: 22,
15596                },
15597            },
15598            new_text: "".to_string(),
15599        }]),
15600        ..Default::default()
15601    };
15602
15603    let closure_completion_item = completion_item.clone();
15604    let counter = Arc::new(AtomicUsize::new(0));
15605    let counter_clone = counter.clone();
15606    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15607        let task_completion_item = closure_completion_item.clone();
15608        counter_clone.fetch_add(1, atomic::Ordering::Release);
15609        async move {
15610            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15611                is_incomplete: true,
15612                item_defaults: None,
15613                items: vec![task_completion_item],
15614            })))
15615        }
15616    });
15617
15618    cx.condition(|editor, _| editor.context_menu_visible())
15619        .await;
15620    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15621    assert!(request.next().await.is_some());
15622    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15623
15624    cx.simulate_keystrokes("S o m");
15625    cx.condition(|editor, _| editor.context_menu_visible())
15626        .await;
15627    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15628    assert!(request.next().await.is_some());
15629    assert!(request.next().await.is_some());
15630    assert!(request.next().await.is_some());
15631    request.close();
15632    assert!(request.next().await.is_none());
15633    assert_eq!(
15634        counter.load(atomic::Ordering::Acquire),
15635        4,
15636        "With the completions menu open, only one LSP request should happen per input"
15637    );
15638}
15639
15640#[gpui::test]
15641async fn test_toggle_comment(cx: &mut TestAppContext) {
15642    init_test(cx, |_| {});
15643    let mut cx = EditorTestContext::new(cx).await;
15644    let language = Arc::new(Language::new(
15645        LanguageConfig {
15646            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15647            ..Default::default()
15648        },
15649        Some(tree_sitter_rust::LANGUAGE.into()),
15650    ));
15651    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15652
15653    // If multiple selections intersect a line, the line is only toggled once.
15654    cx.set_state(indoc! {"
15655        fn a() {
15656            «//b();
15657            ˇ»// «c();
15658            //ˇ»  d();
15659        }
15660    "});
15661
15662    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15663
15664    cx.assert_editor_state(indoc! {"
15665        fn a() {
15666            «b();
15667            c();
15668            ˇ» d();
15669        }
15670    "});
15671
15672    // The comment prefix is inserted at the same column for every line in a
15673    // selection.
15674    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15675
15676    cx.assert_editor_state(indoc! {"
15677        fn a() {
15678            // «b();
15679            // c();
15680            ˇ»//  d();
15681        }
15682    "});
15683
15684    // If a selection ends at the beginning of a line, that line is not toggled.
15685    cx.set_selections_state(indoc! {"
15686        fn a() {
15687            // b();
15688            «// c();
15689        ˇ»    //  d();
15690        }
15691    "});
15692
15693    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15694
15695    cx.assert_editor_state(indoc! {"
15696        fn a() {
15697            // b();
15698            «c();
15699        ˇ»    //  d();
15700        }
15701    "});
15702
15703    // If a selection span a single line and is empty, the line is toggled.
15704    cx.set_state(indoc! {"
15705        fn a() {
15706            a();
15707            b();
15708        ˇ
15709        }
15710    "});
15711
15712    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15713
15714    cx.assert_editor_state(indoc! {"
15715        fn a() {
15716            a();
15717            b();
15718        //•ˇ
15719        }
15720    "});
15721
15722    // If a selection span multiple lines, empty lines are not toggled.
15723    cx.set_state(indoc! {"
15724        fn a() {
15725            «a();
15726
15727            c();ˇ»
15728        }
15729    "});
15730
15731    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15732
15733    cx.assert_editor_state(indoc! {"
15734        fn a() {
15735            // «a();
15736
15737            // c();ˇ»
15738        }
15739    "});
15740
15741    // If a selection includes multiple comment prefixes, all lines are uncommented.
15742    cx.set_state(indoc! {"
15743        fn a() {
15744            «// a();
15745            /// b();
15746            //! c();ˇ»
15747        }
15748    "});
15749
15750    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15751
15752    cx.assert_editor_state(indoc! {"
15753        fn a() {
15754            «a();
15755            b();
15756            c();ˇ»
15757        }
15758    "});
15759}
15760
15761#[gpui::test]
15762async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15763    init_test(cx, |_| {});
15764    let mut cx = EditorTestContext::new(cx).await;
15765    let language = Arc::new(Language::new(
15766        LanguageConfig {
15767            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15768            ..Default::default()
15769        },
15770        Some(tree_sitter_rust::LANGUAGE.into()),
15771    ));
15772    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15773
15774    let toggle_comments = &ToggleComments {
15775        advance_downwards: false,
15776        ignore_indent: true,
15777    };
15778
15779    // If multiple selections intersect a line, the line is only toggled once.
15780    cx.set_state(indoc! {"
15781        fn a() {
15782        //    «b();
15783        //    c();
15784        //    ˇ» d();
15785        }
15786    "});
15787
15788    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15789
15790    cx.assert_editor_state(indoc! {"
15791        fn a() {
15792            «b();
15793            c();
15794            ˇ» d();
15795        }
15796    "});
15797
15798    // The comment prefix is inserted at the beginning of each line
15799    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15800
15801    cx.assert_editor_state(indoc! {"
15802        fn a() {
15803        //    «b();
15804        //    c();
15805        //    ˇ» d();
15806        }
15807    "});
15808
15809    // If a selection ends at the beginning of a line, that line is not toggled.
15810    cx.set_selections_state(indoc! {"
15811        fn a() {
15812        //    b();
15813        //    «c();
15814        ˇ»//     d();
15815        }
15816    "});
15817
15818    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15819
15820    cx.assert_editor_state(indoc! {"
15821        fn a() {
15822        //    b();
15823            «c();
15824        ˇ»//     d();
15825        }
15826    "});
15827
15828    // If a selection span a single line and is empty, the line is toggled.
15829    cx.set_state(indoc! {"
15830        fn a() {
15831            a();
15832            b();
15833        ˇ
15834        }
15835    "});
15836
15837    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15838
15839    cx.assert_editor_state(indoc! {"
15840        fn a() {
15841            a();
15842            b();
15843        //ˇ
15844        }
15845    "});
15846
15847    // If a selection span multiple lines, empty lines are not toggled.
15848    cx.set_state(indoc! {"
15849        fn a() {
15850            «a();
15851
15852            c();ˇ»
15853        }
15854    "});
15855
15856    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15857
15858    cx.assert_editor_state(indoc! {"
15859        fn a() {
15860        //    «a();
15861
15862        //    c();ˇ»
15863        }
15864    "});
15865
15866    // If a selection includes multiple comment prefixes, all lines are uncommented.
15867    cx.set_state(indoc! {"
15868        fn a() {
15869        //    «a();
15870        ///    b();
15871        //!    c();ˇ»
15872        }
15873    "});
15874
15875    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15876
15877    cx.assert_editor_state(indoc! {"
15878        fn a() {
15879            «a();
15880            b();
15881            c();ˇ»
15882        }
15883    "});
15884}
15885
15886#[gpui::test]
15887async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15888    init_test(cx, |_| {});
15889
15890    let language = Arc::new(Language::new(
15891        LanguageConfig {
15892            line_comments: vec!["// ".into()],
15893            ..Default::default()
15894        },
15895        Some(tree_sitter_rust::LANGUAGE.into()),
15896    ));
15897
15898    let mut cx = EditorTestContext::new(cx).await;
15899
15900    cx.language_registry().add(language.clone());
15901    cx.update_buffer(|buffer, cx| {
15902        buffer.set_language(Some(language), cx);
15903    });
15904
15905    let toggle_comments = &ToggleComments {
15906        advance_downwards: true,
15907        ignore_indent: false,
15908    };
15909
15910    // Single cursor on one line -> advance
15911    // Cursor moves horizontally 3 characters as well on non-blank line
15912    cx.set_state(indoc!(
15913        "fn a() {
15914             ˇdog();
15915             cat();
15916        }"
15917    ));
15918    cx.update_editor(|editor, window, cx| {
15919        editor.toggle_comments(toggle_comments, window, cx);
15920    });
15921    cx.assert_editor_state(indoc!(
15922        "fn a() {
15923             // dog();
15924             catˇ();
15925        }"
15926    ));
15927
15928    // Single selection on one line -> don't advance
15929    cx.set_state(indoc!(
15930        "fn a() {
15931             «dog()ˇ»;
15932             cat();
15933        }"
15934    ));
15935    cx.update_editor(|editor, window, cx| {
15936        editor.toggle_comments(toggle_comments, window, cx);
15937    });
15938    cx.assert_editor_state(indoc!(
15939        "fn a() {
15940             // «dog()ˇ»;
15941             cat();
15942        }"
15943    ));
15944
15945    // Multiple cursors on one line -> advance
15946    cx.set_state(indoc!(
15947        "fn a() {
15948             ˇdˇog();
15949             cat();
15950        }"
15951    ));
15952    cx.update_editor(|editor, window, cx| {
15953        editor.toggle_comments(toggle_comments, window, cx);
15954    });
15955    cx.assert_editor_state(indoc!(
15956        "fn a() {
15957             // dog();
15958             catˇ(ˇ);
15959        }"
15960    ));
15961
15962    // Multiple cursors on one line, with selection -> don't advance
15963    cx.set_state(indoc!(
15964        "fn a() {
15965             ˇdˇog«()ˇ»;
15966             cat();
15967        }"
15968    ));
15969    cx.update_editor(|editor, window, cx| {
15970        editor.toggle_comments(toggle_comments, window, cx);
15971    });
15972    cx.assert_editor_state(indoc!(
15973        "fn a() {
15974             // ˇdˇog«()ˇ»;
15975             cat();
15976        }"
15977    ));
15978
15979    // Single cursor on one line -> advance
15980    // Cursor moves to column 0 on blank line
15981    cx.set_state(indoc!(
15982        "fn a() {
15983             ˇdog();
15984
15985             cat();
15986        }"
15987    ));
15988    cx.update_editor(|editor, window, cx| {
15989        editor.toggle_comments(toggle_comments, window, cx);
15990    });
15991    cx.assert_editor_state(indoc!(
15992        "fn a() {
15993             // dog();
15994        ˇ
15995             cat();
15996        }"
15997    ));
15998
15999    // Single cursor on one line -> advance
16000    // Cursor starts and ends at column 0
16001    cx.set_state(indoc!(
16002        "fn a() {
16003         ˇ    dog();
16004             cat();
16005        }"
16006    ));
16007    cx.update_editor(|editor, window, cx| {
16008        editor.toggle_comments(toggle_comments, window, cx);
16009    });
16010    cx.assert_editor_state(indoc!(
16011        "fn a() {
16012             // dog();
16013         ˇ    cat();
16014        }"
16015    ));
16016}
16017
16018#[gpui::test]
16019async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16020    init_test(cx, |_| {});
16021
16022    let mut cx = EditorTestContext::new(cx).await;
16023
16024    let html_language = Arc::new(
16025        Language::new(
16026            LanguageConfig {
16027                name: "HTML".into(),
16028                block_comment: Some(BlockCommentConfig {
16029                    start: "<!-- ".into(),
16030                    prefix: "".into(),
16031                    end: " -->".into(),
16032                    tab_size: 0,
16033                }),
16034                ..Default::default()
16035            },
16036            Some(tree_sitter_html::LANGUAGE.into()),
16037        )
16038        .with_injection_query(
16039            r#"
16040            (script_element
16041                (raw_text) @injection.content
16042                (#set! injection.language "javascript"))
16043            "#,
16044        )
16045        .unwrap(),
16046    );
16047
16048    let javascript_language = Arc::new(Language::new(
16049        LanguageConfig {
16050            name: "JavaScript".into(),
16051            line_comments: vec!["// ".into()],
16052            ..Default::default()
16053        },
16054        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16055    ));
16056
16057    cx.language_registry().add(html_language.clone());
16058    cx.language_registry().add(javascript_language);
16059    cx.update_buffer(|buffer, cx| {
16060        buffer.set_language(Some(html_language), cx);
16061    });
16062
16063    // Toggle comments for empty selections
16064    cx.set_state(
16065        &r#"
16066            <p>A</p>ˇ
16067            <p>B</p>ˇ
16068            <p>C</p>ˇ
16069        "#
16070        .unindent(),
16071    );
16072    cx.update_editor(|editor, window, cx| {
16073        editor.toggle_comments(&ToggleComments::default(), window, cx)
16074    });
16075    cx.assert_editor_state(
16076        &r#"
16077            <!-- <p>A</p>ˇ -->
16078            <!-- <p>B</p>ˇ -->
16079            <!-- <p>C</p>ˇ -->
16080        "#
16081        .unindent(),
16082    );
16083    cx.update_editor(|editor, window, cx| {
16084        editor.toggle_comments(&ToggleComments::default(), window, cx)
16085    });
16086    cx.assert_editor_state(
16087        &r#"
16088            <p>A</p>ˇ
16089            <p>B</p>ˇ
16090            <p>C</p>ˇ
16091        "#
16092        .unindent(),
16093    );
16094
16095    // Toggle comments for mixture of empty and non-empty selections, where
16096    // multiple selections occupy a given line.
16097    cx.set_state(
16098        &r#"
16099            <p>A«</p>
16100            <p>ˇ»B</p>ˇ
16101            <p>C«</p>
16102            <p>ˇ»D</p>ˇ
16103        "#
16104        .unindent(),
16105    );
16106
16107    cx.update_editor(|editor, window, cx| {
16108        editor.toggle_comments(&ToggleComments::default(), window, cx)
16109    });
16110    cx.assert_editor_state(
16111        &r#"
16112            <!-- <p>A«</p>
16113            <p>ˇ»B</p>ˇ -->
16114            <!-- <p>C«</p>
16115            <p>ˇ»D</p>ˇ -->
16116        "#
16117        .unindent(),
16118    );
16119    cx.update_editor(|editor, window, cx| {
16120        editor.toggle_comments(&ToggleComments::default(), window, cx)
16121    });
16122    cx.assert_editor_state(
16123        &r#"
16124            <p>A«</p>
16125            <p>ˇ»B</p>ˇ
16126            <p>C«</p>
16127            <p>ˇ»D</p>ˇ
16128        "#
16129        .unindent(),
16130    );
16131
16132    // Toggle comments when different languages are active for different
16133    // selections.
16134    cx.set_state(
16135        &r#"
16136            ˇ<script>
16137                ˇvar x = new Y();
16138            ˇ</script>
16139        "#
16140        .unindent(),
16141    );
16142    cx.executor().run_until_parked();
16143    cx.update_editor(|editor, window, cx| {
16144        editor.toggle_comments(&ToggleComments::default(), window, cx)
16145    });
16146    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16147    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16148    cx.assert_editor_state(
16149        &r#"
16150            <!-- ˇ<script> -->
16151                // ˇvar x = new Y();
16152            <!-- ˇ</script> -->
16153        "#
16154        .unindent(),
16155    );
16156}
16157
16158#[gpui::test]
16159fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16160    init_test(cx, |_| {});
16161
16162    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16163    let multibuffer = cx.new(|cx| {
16164        let mut multibuffer = MultiBuffer::new(ReadWrite);
16165        multibuffer.push_excerpts(
16166            buffer.clone(),
16167            [
16168                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16169                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16170            ],
16171            cx,
16172        );
16173        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16174        multibuffer
16175    });
16176
16177    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16178    editor.update_in(cx, |editor, window, cx| {
16179        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16180        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16181            s.select_ranges([
16182                Point::new(0, 0)..Point::new(0, 0),
16183                Point::new(1, 0)..Point::new(1, 0),
16184            ])
16185        });
16186
16187        editor.handle_input("X", window, cx);
16188        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16189        assert_eq!(
16190            editor.selections.ranges(&editor.display_snapshot(cx)),
16191            [
16192                Point::new(0, 1)..Point::new(0, 1),
16193                Point::new(1, 1)..Point::new(1, 1),
16194            ]
16195        );
16196
16197        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16198        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16199            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16200        });
16201        editor.backspace(&Default::default(), window, cx);
16202        assert_eq!(editor.text(cx), "Xa\nbbb");
16203        assert_eq!(
16204            editor.selections.ranges(&editor.display_snapshot(cx)),
16205            [Point::new(1, 0)..Point::new(1, 0)]
16206        );
16207
16208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16209            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16210        });
16211        editor.backspace(&Default::default(), window, cx);
16212        assert_eq!(editor.text(cx), "X\nbb");
16213        assert_eq!(
16214            editor.selections.ranges(&editor.display_snapshot(cx)),
16215            [Point::new(0, 1)..Point::new(0, 1)]
16216        );
16217    });
16218}
16219
16220#[gpui::test]
16221fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16222    init_test(cx, |_| {});
16223
16224    let markers = vec![('[', ']').into(), ('(', ')').into()];
16225    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16226        indoc! {"
16227            [aaaa
16228            (bbbb]
16229            cccc)",
16230        },
16231        markers.clone(),
16232    );
16233    let excerpt_ranges = markers.into_iter().map(|marker| {
16234        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16235        ExcerptRange::new(context)
16236    });
16237    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16238    let multibuffer = cx.new(|cx| {
16239        let mut multibuffer = MultiBuffer::new(ReadWrite);
16240        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16241        multibuffer
16242    });
16243
16244    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16245    editor.update_in(cx, |editor, window, cx| {
16246        let (expected_text, selection_ranges) = marked_text_ranges(
16247            indoc! {"
16248                aaaa
16249                bˇbbb
16250                bˇbbˇb
16251                cccc"
16252            },
16253            true,
16254        );
16255        assert_eq!(editor.text(cx), expected_text);
16256        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16257            s.select_ranges(selection_ranges)
16258        });
16259
16260        editor.handle_input("X", window, cx);
16261
16262        let (expected_text, expected_selections) = marked_text_ranges(
16263            indoc! {"
16264                aaaa
16265                bXˇbbXb
16266                bXˇbbXˇb
16267                cccc"
16268            },
16269            false,
16270        );
16271        assert_eq!(editor.text(cx), expected_text);
16272        assert_eq!(
16273            editor.selections.ranges(&editor.display_snapshot(cx)),
16274            expected_selections
16275        );
16276
16277        editor.newline(&Newline, window, cx);
16278        let (expected_text, expected_selections) = marked_text_ranges(
16279            indoc! {"
16280                aaaa
16281                bX
16282                ˇbbX
16283                b
16284                bX
16285                ˇbbX
16286                ˇb
16287                cccc"
16288            },
16289            false,
16290        );
16291        assert_eq!(editor.text(cx), expected_text);
16292        assert_eq!(
16293            editor.selections.ranges(&editor.display_snapshot(cx)),
16294            expected_selections
16295        );
16296    });
16297}
16298
16299#[gpui::test]
16300fn test_refresh_selections(cx: &mut TestAppContext) {
16301    init_test(cx, |_| {});
16302
16303    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16304    let mut excerpt1_id = None;
16305    let multibuffer = cx.new(|cx| {
16306        let mut multibuffer = MultiBuffer::new(ReadWrite);
16307        excerpt1_id = multibuffer
16308            .push_excerpts(
16309                buffer.clone(),
16310                [
16311                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16312                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16313                ],
16314                cx,
16315            )
16316            .into_iter()
16317            .next();
16318        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16319        multibuffer
16320    });
16321
16322    let editor = cx.add_window(|window, cx| {
16323        let mut editor = build_editor(multibuffer.clone(), window, cx);
16324        let snapshot = editor.snapshot(window, cx);
16325        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16326            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16327        });
16328        editor.begin_selection(
16329            Point::new(2, 1).to_display_point(&snapshot),
16330            true,
16331            1,
16332            window,
16333            cx,
16334        );
16335        assert_eq!(
16336            editor.selections.ranges(&editor.display_snapshot(cx)),
16337            [
16338                Point::new(1, 3)..Point::new(1, 3),
16339                Point::new(2, 1)..Point::new(2, 1),
16340            ]
16341        );
16342        editor
16343    });
16344
16345    // Refreshing selections is a no-op when excerpts haven't changed.
16346    _ = editor.update(cx, |editor, window, cx| {
16347        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16348        assert_eq!(
16349            editor.selections.ranges(&editor.display_snapshot(cx)),
16350            [
16351                Point::new(1, 3)..Point::new(1, 3),
16352                Point::new(2, 1)..Point::new(2, 1),
16353            ]
16354        );
16355    });
16356
16357    multibuffer.update(cx, |multibuffer, cx| {
16358        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16359    });
16360    _ = editor.update(cx, |editor, window, cx| {
16361        // Removing an excerpt causes the first selection to become degenerate.
16362        assert_eq!(
16363            editor.selections.ranges(&editor.display_snapshot(cx)),
16364            [
16365                Point::new(0, 0)..Point::new(0, 0),
16366                Point::new(0, 1)..Point::new(0, 1)
16367            ]
16368        );
16369
16370        // Refreshing selections will relocate the first selection to the original buffer
16371        // location.
16372        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16373        assert_eq!(
16374            editor.selections.ranges(&editor.display_snapshot(cx)),
16375            [
16376                Point::new(0, 1)..Point::new(0, 1),
16377                Point::new(0, 3)..Point::new(0, 3)
16378            ]
16379        );
16380        assert!(editor.selections.pending_anchor().is_some());
16381    });
16382}
16383
16384#[gpui::test]
16385fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16386    init_test(cx, |_| {});
16387
16388    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16389    let mut excerpt1_id = None;
16390    let multibuffer = cx.new(|cx| {
16391        let mut multibuffer = MultiBuffer::new(ReadWrite);
16392        excerpt1_id = multibuffer
16393            .push_excerpts(
16394                buffer.clone(),
16395                [
16396                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16397                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16398                ],
16399                cx,
16400            )
16401            .into_iter()
16402            .next();
16403        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16404        multibuffer
16405    });
16406
16407    let editor = cx.add_window(|window, cx| {
16408        let mut editor = build_editor(multibuffer.clone(), window, cx);
16409        let snapshot = editor.snapshot(window, cx);
16410        editor.begin_selection(
16411            Point::new(1, 3).to_display_point(&snapshot),
16412            false,
16413            1,
16414            window,
16415            cx,
16416        );
16417        assert_eq!(
16418            editor.selections.ranges(&editor.display_snapshot(cx)),
16419            [Point::new(1, 3)..Point::new(1, 3)]
16420        );
16421        editor
16422    });
16423
16424    multibuffer.update(cx, |multibuffer, cx| {
16425        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16426    });
16427    _ = editor.update(cx, |editor, window, cx| {
16428        assert_eq!(
16429            editor.selections.ranges(&editor.display_snapshot(cx)),
16430            [Point::new(0, 0)..Point::new(0, 0)]
16431        );
16432
16433        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16434        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16435        assert_eq!(
16436            editor.selections.ranges(&editor.display_snapshot(cx)),
16437            [Point::new(0, 3)..Point::new(0, 3)]
16438        );
16439        assert!(editor.selections.pending_anchor().is_some());
16440    });
16441}
16442
16443#[gpui::test]
16444async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16445    init_test(cx, |_| {});
16446
16447    let language = Arc::new(
16448        Language::new(
16449            LanguageConfig {
16450                brackets: BracketPairConfig {
16451                    pairs: vec![
16452                        BracketPair {
16453                            start: "{".to_string(),
16454                            end: "}".to_string(),
16455                            close: true,
16456                            surround: true,
16457                            newline: true,
16458                        },
16459                        BracketPair {
16460                            start: "/* ".to_string(),
16461                            end: " */".to_string(),
16462                            close: true,
16463                            surround: true,
16464                            newline: true,
16465                        },
16466                    ],
16467                    ..Default::default()
16468                },
16469                ..Default::default()
16470            },
16471            Some(tree_sitter_rust::LANGUAGE.into()),
16472        )
16473        .with_indents_query("")
16474        .unwrap(),
16475    );
16476
16477    let text = concat!(
16478        "{   }\n",     //
16479        "  x\n",       //
16480        "  /*   */\n", //
16481        "x\n",         //
16482        "{{} }\n",     //
16483    );
16484
16485    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16486    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16487    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16488    editor
16489        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16490        .await;
16491
16492    editor.update_in(cx, |editor, window, cx| {
16493        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16494            s.select_display_ranges([
16495                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16496                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16497                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16498            ])
16499        });
16500        editor.newline(&Newline, window, cx);
16501
16502        assert_eq!(
16503            editor.buffer().read(cx).read(cx).text(),
16504            concat!(
16505                "{ \n",    // Suppress rustfmt
16506                "\n",      //
16507                "}\n",     //
16508                "  x\n",   //
16509                "  /* \n", //
16510                "  \n",    //
16511                "  */\n",  //
16512                "x\n",     //
16513                "{{} \n",  //
16514                "}\n",     //
16515            )
16516        );
16517    });
16518}
16519
16520#[gpui::test]
16521fn test_highlighted_ranges(cx: &mut TestAppContext) {
16522    init_test(cx, |_| {});
16523
16524    let editor = cx.add_window(|window, cx| {
16525        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16526        build_editor(buffer, window, cx)
16527    });
16528
16529    _ = editor.update(cx, |editor, window, cx| {
16530        struct Type1;
16531        struct Type2;
16532
16533        let buffer = editor.buffer.read(cx).snapshot(cx);
16534
16535        let anchor_range =
16536            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16537
16538        editor.highlight_background::<Type1>(
16539            &[
16540                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16541                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16542                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16543                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16544            ],
16545            |_| Hsla::red(),
16546            cx,
16547        );
16548        editor.highlight_background::<Type2>(
16549            &[
16550                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16551                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16552                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16553                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16554            ],
16555            |_| Hsla::green(),
16556            cx,
16557        );
16558
16559        let snapshot = editor.snapshot(window, cx);
16560        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16561            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16562            &snapshot,
16563            cx.theme(),
16564        );
16565        assert_eq!(
16566            highlighted_ranges,
16567            &[
16568                (
16569                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16570                    Hsla::green(),
16571                ),
16572                (
16573                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16574                    Hsla::red(),
16575                ),
16576                (
16577                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16578                    Hsla::green(),
16579                ),
16580                (
16581                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16582                    Hsla::red(),
16583                ),
16584            ]
16585        );
16586        assert_eq!(
16587            editor.sorted_background_highlights_in_range(
16588                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16589                &snapshot,
16590                cx.theme(),
16591            ),
16592            &[(
16593                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16594                Hsla::red(),
16595            )]
16596        );
16597    });
16598}
16599
16600#[gpui::test]
16601async fn test_following(cx: &mut TestAppContext) {
16602    init_test(cx, |_| {});
16603
16604    let fs = FakeFs::new(cx.executor());
16605    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16606
16607    let buffer = project.update(cx, |project, cx| {
16608        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16609        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16610    });
16611    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16612    let follower = cx.update(|cx| {
16613        cx.open_window(
16614            WindowOptions {
16615                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16616                    gpui::Point::new(px(0.), px(0.)),
16617                    gpui::Point::new(px(10.), px(80.)),
16618                ))),
16619                ..Default::default()
16620            },
16621            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16622        )
16623        .unwrap()
16624    });
16625
16626    let is_still_following = Rc::new(RefCell::new(true));
16627    let follower_edit_event_count = Rc::new(RefCell::new(0));
16628    let pending_update = Rc::new(RefCell::new(None));
16629    let leader_entity = leader.root(cx).unwrap();
16630    let follower_entity = follower.root(cx).unwrap();
16631    _ = follower.update(cx, {
16632        let update = pending_update.clone();
16633        let is_still_following = is_still_following.clone();
16634        let follower_edit_event_count = follower_edit_event_count.clone();
16635        |_, window, cx| {
16636            cx.subscribe_in(
16637                &leader_entity,
16638                window,
16639                move |_, leader, event, window, cx| {
16640                    leader.read(cx).add_event_to_update_proto(
16641                        event,
16642                        &mut update.borrow_mut(),
16643                        window,
16644                        cx,
16645                    );
16646                },
16647            )
16648            .detach();
16649
16650            cx.subscribe_in(
16651                &follower_entity,
16652                window,
16653                move |_, _, event: &EditorEvent, _window, _cx| {
16654                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16655                        *is_still_following.borrow_mut() = false;
16656                    }
16657
16658                    if let EditorEvent::BufferEdited = event {
16659                        *follower_edit_event_count.borrow_mut() += 1;
16660                    }
16661                },
16662            )
16663            .detach();
16664        }
16665    });
16666
16667    // Update the selections only
16668    _ = leader.update(cx, |leader, window, cx| {
16669        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16670            s.select_ranges([1..1])
16671        });
16672    });
16673    follower
16674        .update(cx, |follower, window, cx| {
16675            follower.apply_update_proto(
16676                &project,
16677                pending_update.borrow_mut().take().unwrap(),
16678                window,
16679                cx,
16680            )
16681        })
16682        .unwrap()
16683        .await
16684        .unwrap();
16685    _ = follower.update(cx, |follower, _, cx| {
16686        assert_eq!(
16687            follower.selections.ranges(&follower.display_snapshot(cx)),
16688            vec![1..1]
16689        );
16690    });
16691    assert!(*is_still_following.borrow());
16692    assert_eq!(*follower_edit_event_count.borrow(), 0);
16693
16694    // Update the scroll position only
16695    _ = leader.update(cx, |leader, window, cx| {
16696        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16697    });
16698    follower
16699        .update(cx, |follower, window, cx| {
16700            follower.apply_update_proto(
16701                &project,
16702                pending_update.borrow_mut().take().unwrap(),
16703                window,
16704                cx,
16705            )
16706        })
16707        .unwrap()
16708        .await
16709        .unwrap();
16710    assert_eq!(
16711        follower
16712            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16713            .unwrap(),
16714        gpui::Point::new(1.5, 3.5)
16715    );
16716    assert!(*is_still_following.borrow());
16717    assert_eq!(*follower_edit_event_count.borrow(), 0);
16718
16719    // Update the selections and scroll position. The follower's scroll position is updated
16720    // via autoscroll, not via the leader's exact scroll position.
16721    _ = leader.update(cx, |leader, window, cx| {
16722        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16723            s.select_ranges([0..0])
16724        });
16725        leader.request_autoscroll(Autoscroll::newest(), cx);
16726        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16727    });
16728    follower
16729        .update(cx, |follower, window, cx| {
16730            follower.apply_update_proto(
16731                &project,
16732                pending_update.borrow_mut().take().unwrap(),
16733                window,
16734                cx,
16735            )
16736        })
16737        .unwrap()
16738        .await
16739        .unwrap();
16740    _ = follower.update(cx, |follower, _, cx| {
16741        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16742        assert_eq!(
16743            follower.selections.ranges(&follower.display_snapshot(cx)),
16744            vec![0..0]
16745        );
16746    });
16747    assert!(*is_still_following.borrow());
16748
16749    // Creating a pending selection that precedes another selection
16750    _ = leader.update(cx, |leader, window, cx| {
16751        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16752            s.select_ranges([1..1])
16753        });
16754        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16755    });
16756    follower
16757        .update(cx, |follower, window, cx| {
16758            follower.apply_update_proto(
16759                &project,
16760                pending_update.borrow_mut().take().unwrap(),
16761                window,
16762                cx,
16763            )
16764        })
16765        .unwrap()
16766        .await
16767        .unwrap();
16768    _ = follower.update(cx, |follower, _, cx| {
16769        assert_eq!(
16770            follower.selections.ranges(&follower.display_snapshot(cx)),
16771            vec![0..0, 1..1]
16772        );
16773    });
16774    assert!(*is_still_following.borrow());
16775
16776    // Extend the pending selection so that it surrounds another selection
16777    _ = leader.update(cx, |leader, window, cx| {
16778        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16779    });
16780    follower
16781        .update(cx, |follower, window, cx| {
16782            follower.apply_update_proto(
16783                &project,
16784                pending_update.borrow_mut().take().unwrap(),
16785                window,
16786                cx,
16787            )
16788        })
16789        .unwrap()
16790        .await
16791        .unwrap();
16792    _ = follower.update(cx, |follower, _, cx| {
16793        assert_eq!(
16794            follower.selections.ranges(&follower.display_snapshot(cx)),
16795            vec![0..2]
16796        );
16797    });
16798
16799    // Scrolling locally breaks the follow
16800    _ = follower.update(cx, |follower, window, cx| {
16801        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16802        follower.set_scroll_anchor(
16803            ScrollAnchor {
16804                anchor: top_anchor,
16805                offset: gpui::Point::new(0.0, 0.5),
16806            },
16807            window,
16808            cx,
16809        );
16810    });
16811    assert!(!(*is_still_following.borrow()));
16812}
16813
16814#[gpui::test]
16815async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16816    init_test(cx, |_| {});
16817
16818    let fs = FakeFs::new(cx.executor());
16819    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16820    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16821    let pane = workspace
16822        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16823        .unwrap();
16824
16825    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16826
16827    let leader = pane.update_in(cx, |_, window, cx| {
16828        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16829        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16830    });
16831
16832    // Start following the editor when it has no excerpts.
16833    let mut state_message =
16834        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16835    let workspace_entity = workspace.root(cx).unwrap();
16836    let follower_1 = cx
16837        .update_window(*workspace.deref(), |_, window, cx| {
16838            Editor::from_state_proto(
16839                workspace_entity,
16840                ViewId {
16841                    creator: CollaboratorId::PeerId(PeerId::default()),
16842                    id: 0,
16843                },
16844                &mut state_message,
16845                window,
16846                cx,
16847            )
16848        })
16849        .unwrap()
16850        .unwrap()
16851        .await
16852        .unwrap();
16853
16854    let update_message = Rc::new(RefCell::new(None));
16855    follower_1.update_in(cx, {
16856        let update = update_message.clone();
16857        |_, window, cx| {
16858            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16859                leader.read(cx).add_event_to_update_proto(
16860                    event,
16861                    &mut update.borrow_mut(),
16862                    window,
16863                    cx,
16864                );
16865            })
16866            .detach();
16867        }
16868    });
16869
16870    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16871        (
16872            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16873            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16874        )
16875    });
16876
16877    // Insert some excerpts.
16878    leader.update(cx, |leader, cx| {
16879        leader.buffer.update(cx, |multibuffer, cx| {
16880            multibuffer.set_excerpts_for_path(
16881                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16882                buffer_1.clone(),
16883                vec![
16884                    Point::row_range(0..3),
16885                    Point::row_range(1..6),
16886                    Point::row_range(12..15),
16887                ],
16888                0,
16889                cx,
16890            );
16891            multibuffer.set_excerpts_for_path(
16892                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16893                buffer_2.clone(),
16894                vec![Point::row_range(0..6), Point::row_range(8..12)],
16895                0,
16896                cx,
16897            );
16898        });
16899    });
16900
16901    // Apply the update of adding the excerpts.
16902    follower_1
16903        .update_in(cx, |follower, window, cx| {
16904            follower.apply_update_proto(
16905                &project,
16906                update_message.borrow().clone().unwrap(),
16907                window,
16908                cx,
16909            )
16910        })
16911        .await
16912        .unwrap();
16913    assert_eq!(
16914        follower_1.update(cx, |editor, cx| editor.text(cx)),
16915        leader.update(cx, |editor, cx| editor.text(cx))
16916    );
16917    update_message.borrow_mut().take();
16918
16919    // Start following separately after it already has excerpts.
16920    let mut state_message =
16921        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16922    let workspace_entity = workspace.root(cx).unwrap();
16923    let follower_2 = cx
16924        .update_window(*workspace.deref(), |_, window, cx| {
16925            Editor::from_state_proto(
16926                workspace_entity,
16927                ViewId {
16928                    creator: CollaboratorId::PeerId(PeerId::default()),
16929                    id: 0,
16930                },
16931                &mut state_message,
16932                window,
16933                cx,
16934            )
16935        })
16936        .unwrap()
16937        .unwrap()
16938        .await
16939        .unwrap();
16940    assert_eq!(
16941        follower_2.update(cx, |editor, cx| editor.text(cx)),
16942        leader.update(cx, |editor, cx| editor.text(cx))
16943    );
16944
16945    // Remove some excerpts.
16946    leader.update(cx, |leader, cx| {
16947        leader.buffer.update(cx, |multibuffer, cx| {
16948            let excerpt_ids = multibuffer.excerpt_ids();
16949            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16950            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16951        });
16952    });
16953
16954    // Apply the update of removing the excerpts.
16955    follower_1
16956        .update_in(cx, |follower, window, cx| {
16957            follower.apply_update_proto(
16958                &project,
16959                update_message.borrow().clone().unwrap(),
16960                window,
16961                cx,
16962            )
16963        })
16964        .await
16965        .unwrap();
16966    follower_2
16967        .update_in(cx, |follower, window, cx| {
16968            follower.apply_update_proto(
16969                &project,
16970                update_message.borrow().clone().unwrap(),
16971                window,
16972                cx,
16973            )
16974        })
16975        .await
16976        .unwrap();
16977    update_message.borrow_mut().take();
16978    assert_eq!(
16979        follower_1.update(cx, |editor, cx| editor.text(cx)),
16980        leader.update(cx, |editor, cx| editor.text(cx))
16981    );
16982}
16983
16984#[gpui::test]
16985async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16986    init_test(cx, |_| {});
16987
16988    let mut cx = EditorTestContext::new(cx).await;
16989    let lsp_store =
16990        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16991
16992    cx.set_state(indoc! {"
16993        ˇfn func(abc def: i32) -> u32 {
16994        }
16995    "});
16996
16997    cx.update(|_, cx| {
16998        lsp_store.update(cx, |lsp_store, cx| {
16999            lsp_store
17000                .update_diagnostics(
17001                    LanguageServerId(0),
17002                    lsp::PublishDiagnosticsParams {
17003                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17004                        version: None,
17005                        diagnostics: vec![
17006                            lsp::Diagnostic {
17007                                range: lsp::Range::new(
17008                                    lsp::Position::new(0, 11),
17009                                    lsp::Position::new(0, 12),
17010                                ),
17011                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17012                                ..Default::default()
17013                            },
17014                            lsp::Diagnostic {
17015                                range: lsp::Range::new(
17016                                    lsp::Position::new(0, 12),
17017                                    lsp::Position::new(0, 15),
17018                                ),
17019                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17020                                ..Default::default()
17021                            },
17022                            lsp::Diagnostic {
17023                                range: lsp::Range::new(
17024                                    lsp::Position::new(0, 25),
17025                                    lsp::Position::new(0, 28),
17026                                ),
17027                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17028                                ..Default::default()
17029                            },
17030                        ],
17031                    },
17032                    None,
17033                    DiagnosticSourceKind::Pushed,
17034                    &[],
17035                    cx,
17036                )
17037                .unwrap()
17038        });
17039    });
17040
17041    executor.run_until_parked();
17042
17043    cx.update_editor(|editor, window, cx| {
17044        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17045    });
17046
17047    cx.assert_editor_state(indoc! {"
17048        fn func(abc def: i32) -> ˇu32 {
17049        }
17050    "});
17051
17052    cx.update_editor(|editor, window, cx| {
17053        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17054    });
17055
17056    cx.assert_editor_state(indoc! {"
17057        fn func(abc ˇdef: i32) -> u32 {
17058        }
17059    "});
17060
17061    cx.update_editor(|editor, window, cx| {
17062        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17063    });
17064
17065    cx.assert_editor_state(indoc! {"
17066        fn func(abcˇ def: i32) -> u32 {
17067        }
17068    "});
17069
17070    cx.update_editor(|editor, window, cx| {
17071        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17072    });
17073
17074    cx.assert_editor_state(indoc! {"
17075        fn func(abc def: i32) -> ˇu32 {
17076        }
17077    "});
17078}
17079
17080#[gpui::test]
17081async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17082    init_test(cx, |_| {});
17083
17084    let mut cx = EditorTestContext::new(cx).await;
17085
17086    let diff_base = r#"
17087        use some::mod;
17088
17089        const A: u32 = 42;
17090
17091        fn main() {
17092            println!("hello");
17093
17094            println!("world");
17095        }
17096        "#
17097    .unindent();
17098
17099    // Edits are modified, removed, modified, added
17100    cx.set_state(
17101        &r#"
17102        use some::modified;
17103
17104        ˇ
17105        fn main() {
17106            println!("hello there");
17107
17108            println!("around the");
17109            println!("world");
17110        }
17111        "#
17112        .unindent(),
17113    );
17114
17115    cx.set_head_text(&diff_base);
17116    executor.run_until_parked();
17117
17118    cx.update_editor(|editor, window, cx| {
17119        //Wrap around the bottom of the buffer
17120        for _ in 0..3 {
17121            editor.go_to_next_hunk(&GoToHunk, window, cx);
17122        }
17123    });
17124
17125    cx.assert_editor_state(
17126        &r#"
17127        ˇuse some::modified;
17128
17129
17130        fn main() {
17131            println!("hello there");
17132
17133            println!("around the");
17134            println!("world");
17135        }
17136        "#
17137        .unindent(),
17138    );
17139
17140    cx.update_editor(|editor, window, cx| {
17141        //Wrap around the top of the buffer
17142        for _ in 0..2 {
17143            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17144        }
17145    });
17146
17147    cx.assert_editor_state(
17148        &r#"
17149        use some::modified;
17150
17151
17152        fn main() {
17153        ˇ    println!("hello there");
17154
17155            println!("around the");
17156            println!("world");
17157        }
17158        "#
17159        .unindent(),
17160    );
17161
17162    cx.update_editor(|editor, window, cx| {
17163        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17164    });
17165
17166    cx.assert_editor_state(
17167        &r#"
17168        use some::modified;
17169
17170        ˇ
17171        fn main() {
17172            println!("hello there");
17173
17174            println!("around the");
17175            println!("world");
17176        }
17177        "#
17178        .unindent(),
17179    );
17180
17181    cx.update_editor(|editor, window, cx| {
17182        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17183    });
17184
17185    cx.assert_editor_state(
17186        &r#"
17187        ˇuse some::modified;
17188
17189
17190        fn main() {
17191            println!("hello there");
17192
17193            println!("around the");
17194            println!("world");
17195        }
17196        "#
17197        .unindent(),
17198    );
17199
17200    cx.update_editor(|editor, window, cx| {
17201        for _ in 0..2 {
17202            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17203        }
17204    });
17205
17206    cx.assert_editor_state(
17207        &r#"
17208        use some::modified;
17209
17210
17211        fn main() {
17212        ˇ    println!("hello there");
17213
17214            println!("around the");
17215            println!("world");
17216        }
17217        "#
17218        .unindent(),
17219    );
17220
17221    cx.update_editor(|editor, window, cx| {
17222        editor.fold(&Fold, window, cx);
17223    });
17224
17225    cx.update_editor(|editor, window, cx| {
17226        editor.go_to_next_hunk(&GoToHunk, window, cx);
17227    });
17228
17229    cx.assert_editor_state(
17230        &r#"
17231        ˇuse some::modified;
17232
17233
17234        fn main() {
17235            println!("hello there");
17236
17237            println!("around the");
17238            println!("world");
17239        }
17240        "#
17241        .unindent(),
17242    );
17243}
17244
17245#[test]
17246fn test_split_words() {
17247    fn split(text: &str) -> Vec<&str> {
17248        split_words(text).collect()
17249    }
17250
17251    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17252    assert_eq!(split("hello_world"), &["hello_", "world"]);
17253    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17254    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17255    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17256    assert_eq!(split("helloworld"), &["helloworld"]);
17257
17258    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17259}
17260
17261#[gpui::test]
17262async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17263    init_test(cx, |_| {});
17264
17265    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17266    let mut assert = |before, after| {
17267        let _state_context = cx.set_state(before);
17268        cx.run_until_parked();
17269        cx.update_editor(|editor, window, cx| {
17270            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17271        });
17272        cx.run_until_parked();
17273        cx.assert_editor_state(after);
17274    };
17275
17276    // Outside bracket jumps to outside of matching bracket
17277    assert("console.logˇ(var);", "console.log(var)ˇ;");
17278    assert("console.log(var)ˇ;", "console.logˇ(var);");
17279
17280    // Inside bracket jumps to inside of matching bracket
17281    assert("console.log(ˇvar);", "console.log(varˇ);");
17282    assert("console.log(varˇ);", "console.log(ˇvar);");
17283
17284    // When outside a bracket and inside, favor jumping to the inside bracket
17285    assert(
17286        "console.log('foo', [1, 2, 3]ˇ);",
17287        "console.log(ˇ'foo', [1, 2, 3]);",
17288    );
17289    assert(
17290        "console.log(ˇ'foo', [1, 2, 3]);",
17291        "console.log('foo', [1, 2, 3]ˇ);",
17292    );
17293
17294    // Bias forward if two options are equally likely
17295    assert(
17296        "let result = curried_fun()ˇ();",
17297        "let result = curried_fun()()ˇ;",
17298    );
17299
17300    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17301    assert(
17302        indoc! {"
17303            function test() {
17304                console.log('test')ˇ
17305            }"},
17306        indoc! {"
17307            function test() {
17308                console.logˇ('test')
17309            }"},
17310    );
17311}
17312
17313#[gpui::test]
17314async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17315    init_test(cx, |_| {});
17316
17317    let fs = FakeFs::new(cx.executor());
17318    fs.insert_tree(
17319        path!("/a"),
17320        json!({
17321            "main.rs": "fn main() { let a = 5; }",
17322            "other.rs": "// Test file",
17323        }),
17324    )
17325    .await;
17326    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17327
17328    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17329    language_registry.add(Arc::new(Language::new(
17330        LanguageConfig {
17331            name: "Rust".into(),
17332            matcher: LanguageMatcher {
17333                path_suffixes: vec!["rs".to_string()],
17334                ..Default::default()
17335            },
17336            brackets: BracketPairConfig {
17337                pairs: vec![BracketPair {
17338                    start: "{".to_string(),
17339                    end: "}".to_string(),
17340                    close: true,
17341                    surround: true,
17342                    newline: true,
17343                }],
17344                disabled_scopes_by_bracket_ix: Vec::new(),
17345            },
17346            ..Default::default()
17347        },
17348        Some(tree_sitter_rust::LANGUAGE.into()),
17349    )));
17350    let mut fake_servers = language_registry.register_fake_lsp(
17351        "Rust",
17352        FakeLspAdapter {
17353            capabilities: lsp::ServerCapabilities {
17354                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17355                    first_trigger_character: "{".to_string(),
17356                    more_trigger_character: None,
17357                }),
17358                ..Default::default()
17359            },
17360            ..Default::default()
17361        },
17362    );
17363
17364    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17365
17366    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17367
17368    let worktree_id = workspace
17369        .update(cx, |workspace, _, cx| {
17370            workspace.project().update(cx, |project, cx| {
17371                project.worktrees(cx).next().unwrap().read(cx).id()
17372            })
17373        })
17374        .unwrap();
17375
17376    let buffer = project
17377        .update(cx, |project, cx| {
17378            project.open_local_buffer(path!("/a/main.rs"), cx)
17379        })
17380        .await
17381        .unwrap();
17382    let editor_handle = workspace
17383        .update(cx, |workspace, window, cx| {
17384            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17385        })
17386        .unwrap()
17387        .await
17388        .unwrap()
17389        .downcast::<Editor>()
17390        .unwrap();
17391
17392    cx.executor().start_waiting();
17393    let fake_server = fake_servers.next().await.unwrap();
17394
17395    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17396        |params, _| async move {
17397            assert_eq!(
17398                params.text_document_position.text_document.uri,
17399                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17400            );
17401            assert_eq!(
17402                params.text_document_position.position,
17403                lsp::Position::new(0, 21),
17404            );
17405
17406            Ok(Some(vec![lsp::TextEdit {
17407                new_text: "]".to_string(),
17408                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17409            }]))
17410        },
17411    );
17412
17413    editor_handle.update_in(cx, |editor, window, cx| {
17414        window.focus(&editor.focus_handle(cx));
17415        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17416            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17417        });
17418        editor.handle_input("{", window, cx);
17419    });
17420
17421    cx.executor().run_until_parked();
17422
17423    buffer.update(cx, |buffer, _| {
17424        assert_eq!(
17425            buffer.text(),
17426            "fn main() { let a = {5}; }",
17427            "No extra braces from on type formatting should appear in the buffer"
17428        )
17429    });
17430}
17431
17432#[gpui::test(iterations = 20, seeds(31))]
17433async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17434    init_test(cx, |_| {});
17435
17436    let mut cx = EditorLspTestContext::new_rust(
17437        lsp::ServerCapabilities {
17438            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17439                first_trigger_character: ".".to_string(),
17440                more_trigger_character: None,
17441            }),
17442            ..Default::default()
17443        },
17444        cx,
17445    )
17446    .await;
17447
17448    cx.update_buffer(|buffer, _| {
17449        // This causes autoindent to be async.
17450        buffer.set_sync_parse_timeout(Duration::ZERO)
17451    });
17452
17453    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17454    cx.simulate_keystroke("\n");
17455    cx.run_until_parked();
17456
17457    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17458    let mut request =
17459        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17460            let buffer_cloned = buffer_cloned.clone();
17461            async move {
17462                buffer_cloned.update(&mut cx, |buffer, _| {
17463                    assert_eq!(
17464                        buffer.text(),
17465                        "fn c() {\n    d()\n        .\n}\n",
17466                        "OnTypeFormatting should triggered after autoindent applied"
17467                    )
17468                })?;
17469
17470                Ok(Some(vec![]))
17471            }
17472        });
17473
17474    cx.simulate_keystroke(".");
17475    cx.run_until_parked();
17476
17477    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17478    assert!(request.next().await.is_some());
17479    request.close();
17480    assert!(request.next().await.is_none());
17481}
17482
17483#[gpui::test]
17484async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17485    init_test(cx, |_| {});
17486
17487    let fs = FakeFs::new(cx.executor());
17488    fs.insert_tree(
17489        path!("/a"),
17490        json!({
17491            "main.rs": "fn main() { let a = 5; }",
17492            "other.rs": "// Test file",
17493        }),
17494    )
17495    .await;
17496
17497    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17498
17499    let server_restarts = Arc::new(AtomicUsize::new(0));
17500    let closure_restarts = Arc::clone(&server_restarts);
17501    let language_server_name = "test language server";
17502    let language_name: LanguageName = "Rust".into();
17503
17504    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17505    language_registry.add(Arc::new(Language::new(
17506        LanguageConfig {
17507            name: language_name.clone(),
17508            matcher: LanguageMatcher {
17509                path_suffixes: vec!["rs".to_string()],
17510                ..Default::default()
17511            },
17512            ..Default::default()
17513        },
17514        Some(tree_sitter_rust::LANGUAGE.into()),
17515    )));
17516    let mut fake_servers = language_registry.register_fake_lsp(
17517        "Rust",
17518        FakeLspAdapter {
17519            name: language_server_name,
17520            initialization_options: Some(json!({
17521                "testOptionValue": true
17522            })),
17523            initializer: Some(Box::new(move |fake_server| {
17524                let task_restarts = Arc::clone(&closure_restarts);
17525                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17526                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17527                    futures::future::ready(Ok(()))
17528                });
17529            })),
17530            ..Default::default()
17531        },
17532    );
17533
17534    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17535    let _buffer = project
17536        .update(cx, |project, cx| {
17537            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17538        })
17539        .await
17540        .unwrap();
17541    let _fake_server = fake_servers.next().await.unwrap();
17542    update_test_language_settings(cx, |language_settings| {
17543        language_settings.languages.0.insert(
17544            language_name.clone().0,
17545            LanguageSettingsContent {
17546                tab_size: NonZeroU32::new(8),
17547                ..Default::default()
17548            },
17549        );
17550    });
17551    cx.executor().run_until_parked();
17552    assert_eq!(
17553        server_restarts.load(atomic::Ordering::Acquire),
17554        0,
17555        "Should not restart LSP server on an unrelated change"
17556    );
17557
17558    update_test_project_settings(cx, |project_settings| {
17559        project_settings.lsp.insert(
17560            "Some other server name".into(),
17561            LspSettings {
17562                binary: None,
17563                settings: None,
17564                initialization_options: Some(json!({
17565                    "some other init value": false
17566                })),
17567                enable_lsp_tasks: false,
17568                fetch: None,
17569            },
17570        );
17571    });
17572    cx.executor().run_until_parked();
17573    assert_eq!(
17574        server_restarts.load(atomic::Ordering::Acquire),
17575        0,
17576        "Should not restart LSP server on an unrelated LSP settings change"
17577    );
17578
17579    update_test_project_settings(cx, |project_settings| {
17580        project_settings.lsp.insert(
17581            language_server_name.into(),
17582            LspSettings {
17583                binary: None,
17584                settings: None,
17585                initialization_options: Some(json!({
17586                    "anotherInitValue": false
17587                })),
17588                enable_lsp_tasks: false,
17589                fetch: None,
17590            },
17591        );
17592    });
17593    cx.executor().run_until_parked();
17594    assert_eq!(
17595        server_restarts.load(atomic::Ordering::Acquire),
17596        1,
17597        "Should restart LSP server on a related LSP settings change"
17598    );
17599
17600    update_test_project_settings(cx, |project_settings| {
17601        project_settings.lsp.insert(
17602            language_server_name.into(),
17603            LspSettings {
17604                binary: None,
17605                settings: None,
17606                initialization_options: Some(json!({
17607                    "anotherInitValue": false
17608                })),
17609                enable_lsp_tasks: false,
17610                fetch: None,
17611            },
17612        );
17613    });
17614    cx.executor().run_until_parked();
17615    assert_eq!(
17616        server_restarts.load(atomic::Ordering::Acquire),
17617        1,
17618        "Should not restart LSP server on a related LSP settings change that is the same"
17619    );
17620
17621    update_test_project_settings(cx, |project_settings| {
17622        project_settings.lsp.insert(
17623            language_server_name.into(),
17624            LspSettings {
17625                binary: None,
17626                settings: None,
17627                initialization_options: None,
17628                enable_lsp_tasks: false,
17629                fetch: None,
17630            },
17631        );
17632    });
17633    cx.executor().run_until_parked();
17634    assert_eq!(
17635        server_restarts.load(atomic::Ordering::Acquire),
17636        2,
17637        "Should restart LSP server on another related LSP settings change"
17638    );
17639}
17640
17641#[gpui::test]
17642async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17643    init_test(cx, |_| {});
17644
17645    let mut cx = EditorLspTestContext::new_rust(
17646        lsp::ServerCapabilities {
17647            completion_provider: Some(lsp::CompletionOptions {
17648                trigger_characters: Some(vec![".".to_string()]),
17649                resolve_provider: Some(true),
17650                ..Default::default()
17651            }),
17652            ..Default::default()
17653        },
17654        cx,
17655    )
17656    .await;
17657
17658    cx.set_state("fn main() { let a = 2ˇ; }");
17659    cx.simulate_keystroke(".");
17660    let completion_item = lsp::CompletionItem {
17661        label: "some".into(),
17662        kind: Some(lsp::CompletionItemKind::SNIPPET),
17663        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17664        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17665            kind: lsp::MarkupKind::Markdown,
17666            value: "```rust\nSome(2)\n```".to_string(),
17667        })),
17668        deprecated: Some(false),
17669        sort_text: Some("fffffff2".to_string()),
17670        filter_text: Some("some".to_string()),
17671        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17672        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17673            range: lsp::Range {
17674                start: lsp::Position {
17675                    line: 0,
17676                    character: 22,
17677                },
17678                end: lsp::Position {
17679                    line: 0,
17680                    character: 22,
17681                },
17682            },
17683            new_text: "Some(2)".to_string(),
17684        })),
17685        additional_text_edits: Some(vec![lsp::TextEdit {
17686            range: lsp::Range {
17687                start: lsp::Position {
17688                    line: 0,
17689                    character: 20,
17690                },
17691                end: lsp::Position {
17692                    line: 0,
17693                    character: 22,
17694                },
17695            },
17696            new_text: "".to_string(),
17697        }]),
17698        ..Default::default()
17699    };
17700
17701    let closure_completion_item = completion_item.clone();
17702    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17703        let task_completion_item = closure_completion_item.clone();
17704        async move {
17705            Ok(Some(lsp::CompletionResponse::Array(vec![
17706                task_completion_item,
17707            ])))
17708        }
17709    });
17710
17711    request.next().await;
17712
17713    cx.condition(|editor, _| editor.context_menu_visible())
17714        .await;
17715    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17716        editor
17717            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17718            .unwrap()
17719    });
17720    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17721
17722    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17723        let task_completion_item = completion_item.clone();
17724        async move { Ok(task_completion_item) }
17725    })
17726    .next()
17727    .await
17728    .unwrap();
17729    apply_additional_edits.await.unwrap();
17730    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17731}
17732
17733#[gpui::test]
17734async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17735    init_test(cx, |_| {});
17736
17737    let mut cx = EditorLspTestContext::new_rust(
17738        lsp::ServerCapabilities {
17739            completion_provider: Some(lsp::CompletionOptions {
17740                trigger_characters: Some(vec![".".to_string()]),
17741                resolve_provider: Some(true),
17742                ..Default::default()
17743            }),
17744            ..Default::default()
17745        },
17746        cx,
17747    )
17748    .await;
17749
17750    cx.set_state("fn main() { let a = 2ˇ; }");
17751    cx.simulate_keystroke(".");
17752
17753    let item1 = lsp::CompletionItem {
17754        label: "method id()".to_string(),
17755        filter_text: Some("id".to_string()),
17756        detail: None,
17757        documentation: None,
17758        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17759            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17760            new_text: ".id".to_string(),
17761        })),
17762        ..lsp::CompletionItem::default()
17763    };
17764
17765    let item2 = lsp::CompletionItem {
17766        label: "other".to_string(),
17767        filter_text: Some("other".to_string()),
17768        detail: None,
17769        documentation: None,
17770        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17771            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17772            new_text: ".other".to_string(),
17773        })),
17774        ..lsp::CompletionItem::default()
17775    };
17776
17777    let item1 = item1.clone();
17778    cx.set_request_handler::<lsp::request::Completion, _, _>({
17779        let item1 = item1.clone();
17780        move |_, _, _| {
17781            let item1 = item1.clone();
17782            let item2 = item2.clone();
17783            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17784        }
17785    })
17786    .next()
17787    .await;
17788
17789    cx.condition(|editor, _| editor.context_menu_visible())
17790        .await;
17791    cx.update_editor(|editor, _, _| {
17792        let context_menu = editor.context_menu.borrow_mut();
17793        let context_menu = context_menu
17794            .as_ref()
17795            .expect("Should have the context menu deployed");
17796        match context_menu {
17797            CodeContextMenu::Completions(completions_menu) => {
17798                let completions = completions_menu.completions.borrow_mut();
17799                assert_eq!(
17800                    completions
17801                        .iter()
17802                        .map(|completion| &completion.label.text)
17803                        .collect::<Vec<_>>(),
17804                    vec!["method id()", "other"]
17805                )
17806            }
17807            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17808        }
17809    });
17810
17811    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17812        let item1 = item1.clone();
17813        move |_, item_to_resolve, _| {
17814            let item1 = item1.clone();
17815            async move {
17816                if item1 == item_to_resolve {
17817                    Ok(lsp::CompletionItem {
17818                        label: "method id()".to_string(),
17819                        filter_text: Some("id".to_string()),
17820                        detail: Some("Now resolved!".to_string()),
17821                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17822                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17823                            range: lsp::Range::new(
17824                                lsp::Position::new(0, 22),
17825                                lsp::Position::new(0, 22),
17826                            ),
17827                            new_text: ".id".to_string(),
17828                        })),
17829                        ..lsp::CompletionItem::default()
17830                    })
17831                } else {
17832                    Ok(item_to_resolve)
17833                }
17834            }
17835        }
17836    })
17837    .next()
17838    .await
17839    .unwrap();
17840    cx.run_until_parked();
17841
17842    cx.update_editor(|editor, window, cx| {
17843        editor.context_menu_next(&Default::default(), window, cx);
17844    });
17845
17846    cx.update_editor(|editor, _, _| {
17847        let context_menu = editor.context_menu.borrow_mut();
17848        let context_menu = context_menu
17849            .as_ref()
17850            .expect("Should have the context menu deployed");
17851        match context_menu {
17852            CodeContextMenu::Completions(completions_menu) => {
17853                let completions = completions_menu.completions.borrow_mut();
17854                assert_eq!(
17855                    completions
17856                        .iter()
17857                        .map(|completion| &completion.label.text)
17858                        .collect::<Vec<_>>(),
17859                    vec!["method id() Now resolved!", "other"],
17860                    "Should update first completion label, but not second as the filter text did not match."
17861                );
17862            }
17863            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17864        }
17865    });
17866}
17867
17868#[gpui::test]
17869async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17870    init_test(cx, |_| {});
17871    let mut cx = EditorLspTestContext::new_rust(
17872        lsp::ServerCapabilities {
17873            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17874            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17875            completion_provider: Some(lsp::CompletionOptions {
17876                resolve_provider: Some(true),
17877                ..Default::default()
17878            }),
17879            ..Default::default()
17880        },
17881        cx,
17882    )
17883    .await;
17884    cx.set_state(indoc! {"
17885        struct TestStruct {
17886            field: i32
17887        }
17888
17889        fn mainˇ() {
17890            let unused_var = 42;
17891            let test_struct = TestStruct { field: 42 };
17892        }
17893    "});
17894    let symbol_range = cx.lsp_range(indoc! {"
17895        struct TestStruct {
17896            field: i32
17897        }
17898
17899        «fn main»() {
17900            let unused_var = 42;
17901            let test_struct = TestStruct { field: 42 };
17902        }
17903    "});
17904    let mut hover_requests =
17905        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17906            Ok(Some(lsp::Hover {
17907                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17908                    kind: lsp::MarkupKind::Markdown,
17909                    value: "Function documentation".to_string(),
17910                }),
17911                range: Some(symbol_range),
17912            }))
17913        });
17914
17915    // Case 1: Test that code action menu hide hover popover
17916    cx.dispatch_action(Hover);
17917    hover_requests.next().await;
17918    cx.condition(|editor, _| editor.hover_state.visible()).await;
17919    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17920        move |_, _, _| async move {
17921            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17922                lsp::CodeAction {
17923                    title: "Remove unused variable".to_string(),
17924                    kind: Some(CodeActionKind::QUICKFIX),
17925                    edit: Some(lsp::WorkspaceEdit {
17926                        changes: Some(
17927                            [(
17928                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17929                                vec![lsp::TextEdit {
17930                                    range: lsp::Range::new(
17931                                        lsp::Position::new(5, 4),
17932                                        lsp::Position::new(5, 27),
17933                                    ),
17934                                    new_text: "".to_string(),
17935                                }],
17936                            )]
17937                            .into_iter()
17938                            .collect(),
17939                        ),
17940                        ..Default::default()
17941                    }),
17942                    ..Default::default()
17943                },
17944            )]))
17945        },
17946    );
17947    cx.update_editor(|editor, window, cx| {
17948        editor.toggle_code_actions(
17949            &ToggleCodeActions {
17950                deployed_from: None,
17951                quick_launch: false,
17952            },
17953            window,
17954            cx,
17955        );
17956    });
17957    code_action_requests.next().await;
17958    cx.run_until_parked();
17959    cx.condition(|editor, _| editor.context_menu_visible())
17960        .await;
17961    cx.update_editor(|editor, _, _| {
17962        assert!(
17963            !editor.hover_state.visible(),
17964            "Hover popover should be hidden when code action menu is shown"
17965        );
17966        // Hide code actions
17967        editor.context_menu.take();
17968    });
17969
17970    // Case 2: Test that code completions hide hover popover
17971    cx.dispatch_action(Hover);
17972    hover_requests.next().await;
17973    cx.condition(|editor, _| editor.hover_state.visible()).await;
17974    let counter = Arc::new(AtomicUsize::new(0));
17975    let mut completion_requests =
17976        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17977            let counter = counter.clone();
17978            async move {
17979                counter.fetch_add(1, atomic::Ordering::Release);
17980                Ok(Some(lsp::CompletionResponse::Array(vec![
17981                    lsp::CompletionItem {
17982                        label: "main".into(),
17983                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17984                        detail: Some("() -> ()".to_string()),
17985                        ..Default::default()
17986                    },
17987                    lsp::CompletionItem {
17988                        label: "TestStruct".into(),
17989                        kind: Some(lsp::CompletionItemKind::STRUCT),
17990                        detail: Some("struct TestStruct".to_string()),
17991                        ..Default::default()
17992                    },
17993                ])))
17994            }
17995        });
17996    cx.update_editor(|editor, window, cx| {
17997        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17998    });
17999    completion_requests.next().await;
18000    cx.condition(|editor, _| editor.context_menu_visible())
18001        .await;
18002    cx.update_editor(|editor, _, _| {
18003        assert!(
18004            !editor.hover_state.visible(),
18005            "Hover popover should be hidden when completion menu is shown"
18006        );
18007    });
18008}
18009
18010#[gpui::test]
18011async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18012    init_test(cx, |_| {});
18013
18014    let mut cx = EditorLspTestContext::new_rust(
18015        lsp::ServerCapabilities {
18016            completion_provider: Some(lsp::CompletionOptions {
18017                trigger_characters: Some(vec![".".to_string()]),
18018                resolve_provider: Some(true),
18019                ..Default::default()
18020            }),
18021            ..Default::default()
18022        },
18023        cx,
18024    )
18025    .await;
18026
18027    cx.set_state("fn main() { let a = 2ˇ; }");
18028    cx.simulate_keystroke(".");
18029
18030    let unresolved_item_1 = lsp::CompletionItem {
18031        label: "id".to_string(),
18032        filter_text: Some("id".to_string()),
18033        detail: None,
18034        documentation: None,
18035        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18036            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18037            new_text: ".id".to_string(),
18038        })),
18039        ..lsp::CompletionItem::default()
18040    };
18041    let resolved_item_1 = lsp::CompletionItem {
18042        additional_text_edits: Some(vec![lsp::TextEdit {
18043            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18044            new_text: "!!".to_string(),
18045        }]),
18046        ..unresolved_item_1.clone()
18047    };
18048    let unresolved_item_2 = lsp::CompletionItem {
18049        label: "other".to_string(),
18050        filter_text: Some("other".to_string()),
18051        detail: None,
18052        documentation: None,
18053        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18054            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18055            new_text: ".other".to_string(),
18056        })),
18057        ..lsp::CompletionItem::default()
18058    };
18059    let resolved_item_2 = lsp::CompletionItem {
18060        additional_text_edits: Some(vec![lsp::TextEdit {
18061            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18062            new_text: "??".to_string(),
18063        }]),
18064        ..unresolved_item_2.clone()
18065    };
18066
18067    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18068    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18069    cx.lsp
18070        .server
18071        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18072            let unresolved_item_1 = unresolved_item_1.clone();
18073            let resolved_item_1 = resolved_item_1.clone();
18074            let unresolved_item_2 = unresolved_item_2.clone();
18075            let resolved_item_2 = resolved_item_2.clone();
18076            let resolve_requests_1 = resolve_requests_1.clone();
18077            let resolve_requests_2 = resolve_requests_2.clone();
18078            move |unresolved_request, _| {
18079                let unresolved_item_1 = unresolved_item_1.clone();
18080                let resolved_item_1 = resolved_item_1.clone();
18081                let unresolved_item_2 = unresolved_item_2.clone();
18082                let resolved_item_2 = resolved_item_2.clone();
18083                let resolve_requests_1 = resolve_requests_1.clone();
18084                let resolve_requests_2 = resolve_requests_2.clone();
18085                async move {
18086                    if unresolved_request == unresolved_item_1 {
18087                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18088                        Ok(resolved_item_1.clone())
18089                    } else if unresolved_request == unresolved_item_2 {
18090                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18091                        Ok(resolved_item_2.clone())
18092                    } else {
18093                        panic!("Unexpected completion item {unresolved_request:?}")
18094                    }
18095                }
18096            }
18097        })
18098        .detach();
18099
18100    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18101        let unresolved_item_1 = unresolved_item_1.clone();
18102        let unresolved_item_2 = unresolved_item_2.clone();
18103        async move {
18104            Ok(Some(lsp::CompletionResponse::Array(vec![
18105                unresolved_item_1,
18106                unresolved_item_2,
18107            ])))
18108        }
18109    })
18110    .next()
18111    .await;
18112
18113    cx.condition(|editor, _| editor.context_menu_visible())
18114        .await;
18115    cx.update_editor(|editor, _, _| {
18116        let context_menu = editor.context_menu.borrow_mut();
18117        let context_menu = context_menu
18118            .as_ref()
18119            .expect("Should have the context menu deployed");
18120        match context_menu {
18121            CodeContextMenu::Completions(completions_menu) => {
18122                let completions = completions_menu.completions.borrow_mut();
18123                assert_eq!(
18124                    completions
18125                        .iter()
18126                        .map(|completion| &completion.label.text)
18127                        .collect::<Vec<_>>(),
18128                    vec!["id", "other"]
18129                )
18130            }
18131            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18132        }
18133    });
18134    cx.run_until_parked();
18135
18136    cx.update_editor(|editor, window, cx| {
18137        editor.context_menu_next(&ContextMenuNext, window, cx);
18138    });
18139    cx.run_until_parked();
18140    cx.update_editor(|editor, window, cx| {
18141        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18142    });
18143    cx.run_until_parked();
18144    cx.update_editor(|editor, window, cx| {
18145        editor.context_menu_next(&ContextMenuNext, window, cx);
18146    });
18147    cx.run_until_parked();
18148    cx.update_editor(|editor, window, cx| {
18149        editor
18150            .compose_completion(&ComposeCompletion::default(), window, cx)
18151            .expect("No task returned")
18152    })
18153    .await
18154    .expect("Completion failed");
18155    cx.run_until_parked();
18156
18157    cx.update_editor(|editor, _, cx| {
18158        assert_eq!(
18159            resolve_requests_1.load(atomic::Ordering::Acquire),
18160            1,
18161            "Should always resolve once despite multiple selections"
18162        );
18163        assert_eq!(
18164            resolve_requests_2.load(atomic::Ordering::Acquire),
18165            1,
18166            "Should always resolve once after multiple selections and applying the completion"
18167        );
18168        assert_eq!(
18169            editor.text(cx),
18170            "fn main() { let a = ??.other; }",
18171            "Should use resolved data when applying the completion"
18172        );
18173    });
18174}
18175
18176#[gpui::test]
18177async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18178    init_test(cx, |_| {});
18179
18180    let item_0 = lsp::CompletionItem {
18181        label: "abs".into(),
18182        insert_text: Some("abs".into()),
18183        data: Some(json!({ "very": "special"})),
18184        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18185        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18186            lsp::InsertReplaceEdit {
18187                new_text: "abs".to_string(),
18188                insert: lsp::Range::default(),
18189                replace: lsp::Range::default(),
18190            },
18191        )),
18192        ..lsp::CompletionItem::default()
18193    };
18194    let items = iter::once(item_0.clone())
18195        .chain((11..51).map(|i| lsp::CompletionItem {
18196            label: format!("item_{}", i),
18197            insert_text: Some(format!("item_{}", i)),
18198            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18199            ..lsp::CompletionItem::default()
18200        }))
18201        .collect::<Vec<_>>();
18202
18203    let default_commit_characters = vec!["?".to_string()];
18204    let default_data = json!({ "default": "data"});
18205    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18206    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18207    let default_edit_range = lsp::Range {
18208        start: lsp::Position {
18209            line: 0,
18210            character: 5,
18211        },
18212        end: lsp::Position {
18213            line: 0,
18214            character: 5,
18215        },
18216    };
18217
18218    let mut cx = EditorLspTestContext::new_rust(
18219        lsp::ServerCapabilities {
18220            completion_provider: Some(lsp::CompletionOptions {
18221                trigger_characters: Some(vec![".".to_string()]),
18222                resolve_provider: Some(true),
18223                ..Default::default()
18224            }),
18225            ..Default::default()
18226        },
18227        cx,
18228    )
18229    .await;
18230
18231    cx.set_state("fn main() { let a = 2ˇ; }");
18232    cx.simulate_keystroke(".");
18233
18234    let completion_data = default_data.clone();
18235    let completion_characters = default_commit_characters.clone();
18236    let completion_items = items.clone();
18237    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18238        let default_data = completion_data.clone();
18239        let default_commit_characters = completion_characters.clone();
18240        let items = completion_items.clone();
18241        async move {
18242            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18243                items,
18244                item_defaults: Some(lsp::CompletionListItemDefaults {
18245                    data: Some(default_data.clone()),
18246                    commit_characters: Some(default_commit_characters.clone()),
18247                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18248                        default_edit_range,
18249                    )),
18250                    insert_text_format: Some(default_insert_text_format),
18251                    insert_text_mode: Some(default_insert_text_mode),
18252                }),
18253                ..lsp::CompletionList::default()
18254            })))
18255        }
18256    })
18257    .next()
18258    .await;
18259
18260    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18261    cx.lsp
18262        .server
18263        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18264            let closure_resolved_items = resolved_items.clone();
18265            move |item_to_resolve, _| {
18266                let closure_resolved_items = closure_resolved_items.clone();
18267                async move {
18268                    closure_resolved_items.lock().push(item_to_resolve.clone());
18269                    Ok(item_to_resolve)
18270                }
18271            }
18272        })
18273        .detach();
18274
18275    cx.condition(|editor, _| editor.context_menu_visible())
18276        .await;
18277    cx.run_until_parked();
18278    cx.update_editor(|editor, _, _| {
18279        let menu = editor.context_menu.borrow_mut();
18280        match menu.as_ref().expect("should have the completions menu") {
18281            CodeContextMenu::Completions(completions_menu) => {
18282                assert_eq!(
18283                    completions_menu
18284                        .entries
18285                        .borrow()
18286                        .iter()
18287                        .map(|mat| mat.string.clone())
18288                        .collect::<Vec<String>>(),
18289                    items
18290                        .iter()
18291                        .map(|completion| completion.label.clone())
18292                        .collect::<Vec<String>>()
18293                );
18294            }
18295            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18296        }
18297    });
18298    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18299    // with 4 from the end.
18300    assert_eq!(
18301        *resolved_items.lock(),
18302        [&items[0..16], &items[items.len() - 4..items.len()]]
18303            .concat()
18304            .iter()
18305            .cloned()
18306            .map(|mut item| {
18307                if item.data.is_none() {
18308                    item.data = Some(default_data.clone());
18309                }
18310                item
18311            })
18312            .collect::<Vec<lsp::CompletionItem>>(),
18313        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18314    );
18315    resolved_items.lock().clear();
18316
18317    cx.update_editor(|editor, window, cx| {
18318        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18319    });
18320    cx.run_until_parked();
18321    // Completions that have already been resolved are skipped.
18322    assert_eq!(
18323        *resolved_items.lock(),
18324        items[items.len() - 17..items.len() - 4]
18325            .iter()
18326            .cloned()
18327            .map(|mut item| {
18328                if item.data.is_none() {
18329                    item.data = Some(default_data.clone());
18330                }
18331                item
18332            })
18333            .collect::<Vec<lsp::CompletionItem>>()
18334    );
18335    resolved_items.lock().clear();
18336}
18337
18338#[gpui::test]
18339async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18340    init_test(cx, |_| {});
18341
18342    let mut cx = EditorLspTestContext::new(
18343        Language::new(
18344            LanguageConfig {
18345                matcher: LanguageMatcher {
18346                    path_suffixes: vec!["jsx".into()],
18347                    ..Default::default()
18348                },
18349                overrides: [(
18350                    "element".into(),
18351                    LanguageConfigOverride {
18352                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18353                        ..Default::default()
18354                    },
18355                )]
18356                .into_iter()
18357                .collect(),
18358                ..Default::default()
18359            },
18360            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18361        )
18362        .with_override_query("(jsx_self_closing_element) @element")
18363        .unwrap(),
18364        lsp::ServerCapabilities {
18365            completion_provider: Some(lsp::CompletionOptions {
18366                trigger_characters: Some(vec![":".to_string()]),
18367                ..Default::default()
18368            }),
18369            ..Default::default()
18370        },
18371        cx,
18372    )
18373    .await;
18374
18375    cx.lsp
18376        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18377            Ok(Some(lsp::CompletionResponse::Array(vec![
18378                lsp::CompletionItem {
18379                    label: "bg-blue".into(),
18380                    ..Default::default()
18381                },
18382                lsp::CompletionItem {
18383                    label: "bg-red".into(),
18384                    ..Default::default()
18385                },
18386                lsp::CompletionItem {
18387                    label: "bg-yellow".into(),
18388                    ..Default::default()
18389                },
18390            ])))
18391        });
18392
18393    cx.set_state(r#"<p class="bgˇ" />"#);
18394
18395    // Trigger completion when typing a dash, because the dash is an extra
18396    // word character in the 'element' scope, which contains the cursor.
18397    cx.simulate_keystroke("-");
18398    cx.executor().run_until_parked();
18399    cx.update_editor(|editor, _, _| {
18400        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18401        {
18402            assert_eq!(
18403                completion_menu_entries(menu),
18404                &["bg-blue", "bg-red", "bg-yellow"]
18405            );
18406        } else {
18407            panic!("expected completion menu to be open");
18408        }
18409    });
18410
18411    cx.simulate_keystroke("l");
18412    cx.executor().run_until_parked();
18413    cx.update_editor(|editor, _, _| {
18414        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18415        {
18416            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18417        } else {
18418            panic!("expected completion menu to be open");
18419        }
18420    });
18421
18422    // When filtering completions, consider the character after the '-' to
18423    // be the start of a subword.
18424    cx.set_state(r#"<p class="yelˇ" />"#);
18425    cx.simulate_keystroke("l");
18426    cx.executor().run_until_parked();
18427    cx.update_editor(|editor, _, _| {
18428        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18429        {
18430            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18431        } else {
18432            panic!("expected completion menu to be open");
18433        }
18434    });
18435}
18436
18437fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18438    let entries = menu.entries.borrow();
18439    entries.iter().map(|mat| mat.string.clone()).collect()
18440}
18441
18442#[gpui::test]
18443async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18444    init_test(cx, |settings| {
18445        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18446    });
18447
18448    let fs = FakeFs::new(cx.executor());
18449    fs.insert_file(path!("/file.ts"), Default::default()).await;
18450
18451    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18452    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18453
18454    language_registry.add(Arc::new(Language::new(
18455        LanguageConfig {
18456            name: "TypeScript".into(),
18457            matcher: LanguageMatcher {
18458                path_suffixes: vec!["ts".to_string()],
18459                ..Default::default()
18460            },
18461            ..Default::default()
18462        },
18463        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18464    )));
18465    update_test_language_settings(cx, |settings| {
18466        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18467    });
18468
18469    let test_plugin = "test_plugin";
18470    let _ = language_registry.register_fake_lsp(
18471        "TypeScript",
18472        FakeLspAdapter {
18473            prettier_plugins: vec![test_plugin],
18474            ..Default::default()
18475        },
18476    );
18477
18478    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18479    let buffer = project
18480        .update(cx, |project, cx| {
18481            project.open_local_buffer(path!("/file.ts"), cx)
18482        })
18483        .await
18484        .unwrap();
18485
18486    let buffer_text = "one\ntwo\nthree\n";
18487    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18488    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18489    editor.update_in(cx, |editor, window, cx| {
18490        editor.set_text(buffer_text, window, cx)
18491    });
18492
18493    editor
18494        .update_in(cx, |editor, window, cx| {
18495            editor.perform_format(
18496                project.clone(),
18497                FormatTrigger::Manual,
18498                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18499                window,
18500                cx,
18501            )
18502        })
18503        .unwrap()
18504        .await;
18505    assert_eq!(
18506        editor.update(cx, |editor, cx| editor.text(cx)),
18507        buffer_text.to_string() + prettier_format_suffix,
18508        "Test prettier formatting was not applied to the original buffer text",
18509    );
18510
18511    update_test_language_settings(cx, |settings| {
18512        settings.defaults.formatter = Some(FormatterList::default())
18513    });
18514    let format = editor.update_in(cx, |editor, window, cx| {
18515        editor.perform_format(
18516            project.clone(),
18517            FormatTrigger::Manual,
18518            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18519            window,
18520            cx,
18521        )
18522    });
18523    format.await.unwrap();
18524    assert_eq!(
18525        editor.update(cx, |editor, cx| editor.text(cx)),
18526        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18527        "Autoformatting (via test prettier) was not applied to the original buffer text",
18528    );
18529}
18530
18531#[gpui::test]
18532async fn test_addition_reverts(cx: &mut TestAppContext) {
18533    init_test(cx, |_| {});
18534    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18535    let base_text = indoc! {r#"
18536        struct Row;
18537        struct Row1;
18538        struct Row2;
18539
18540        struct Row4;
18541        struct Row5;
18542        struct Row6;
18543
18544        struct Row8;
18545        struct Row9;
18546        struct Row10;"#};
18547
18548    // When addition hunks are not adjacent to carets, no hunk revert is performed
18549    assert_hunk_revert(
18550        indoc! {r#"struct Row;
18551                   struct Row1;
18552                   struct Row1.1;
18553                   struct Row1.2;
18554                   struct Row2;ˇ
18555
18556                   struct Row4;
18557                   struct Row5;
18558                   struct Row6;
18559
18560                   struct Row8;
18561                   ˇstruct Row9;
18562                   struct Row9.1;
18563                   struct Row9.2;
18564                   struct Row9.3;
18565                   struct Row10;"#},
18566        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18567        indoc! {r#"struct Row;
18568                   struct Row1;
18569                   struct Row1.1;
18570                   struct Row1.2;
18571                   struct Row2;ˇ
18572
18573                   struct Row4;
18574                   struct Row5;
18575                   struct Row6;
18576
18577                   struct Row8;
18578                   ˇstruct Row9;
18579                   struct Row9.1;
18580                   struct Row9.2;
18581                   struct Row9.3;
18582                   struct Row10;"#},
18583        base_text,
18584        &mut cx,
18585    );
18586    // Same for selections
18587    assert_hunk_revert(
18588        indoc! {r#"struct Row;
18589                   struct Row1;
18590                   struct Row2;
18591                   struct Row2.1;
18592                   struct Row2.2;
18593                   «ˇ
18594                   struct Row4;
18595                   struct» Row5;
18596                   «struct Row6;
18597                   ˇ»
18598                   struct Row9.1;
18599                   struct Row9.2;
18600                   struct Row9.3;
18601                   struct Row8;
18602                   struct Row9;
18603                   struct Row10;"#},
18604        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18605        indoc! {r#"struct Row;
18606                   struct Row1;
18607                   struct Row2;
18608                   struct Row2.1;
18609                   struct Row2.2;
18610                   «ˇ
18611                   struct Row4;
18612                   struct» Row5;
18613                   «struct Row6;
18614                   ˇ»
18615                   struct Row9.1;
18616                   struct Row9.2;
18617                   struct Row9.3;
18618                   struct Row8;
18619                   struct Row9;
18620                   struct Row10;"#},
18621        base_text,
18622        &mut cx,
18623    );
18624
18625    // When carets and selections intersect the addition hunks, those are reverted.
18626    // Adjacent carets got merged.
18627    assert_hunk_revert(
18628        indoc! {r#"struct Row;
18629                   ˇ// something on the top
18630                   struct Row1;
18631                   struct Row2;
18632                   struct Roˇw3.1;
18633                   struct Row2.2;
18634                   struct Row2.3;ˇ
18635
18636                   struct Row4;
18637                   struct ˇRow5.1;
18638                   struct Row5.2;
18639                   struct «Rowˇ»5.3;
18640                   struct Row5;
18641                   struct Row6;
18642                   ˇ
18643                   struct Row9.1;
18644                   struct «Rowˇ»9.2;
18645                   struct «ˇRow»9.3;
18646                   struct Row8;
18647                   struct Row9;
18648                   «ˇ// something on bottom»
18649                   struct Row10;"#},
18650        vec![
18651            DiffHunkStatusKind::Added,
18652            DiffHunkStatusKind::Added,
18653            DiffHunkStatusKind::Added,
18654            DiffHunkStatusKind::Added,
18655            DiffHunkStatusKind::Added,
18656        ],
18657        indoc! {r#"struct Row;
18658                   ˇstruct Row1;
18659                   struct Row2;
18660                   ˇ
18661                   struct Row4;
18662                   ˇstruct Row5;
18663                   struct Row6;
18664                   ˇ
18665                   ˇstruct Row8;
18666                   struct Row9;
18667                   ˇstruct Row10;"#},
18668        base_text,
18669        &mut cx,
18670    );
18671}
18672
18673#[gpui::test]
18674async fn test_modification_reverts(cx: &mut TestAppContext) {
18675    init_test(cx, |_| {});
18676    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18677    let base_text = indoc! {r#"
18678        struct Row;
18679        struct Row1;
18680        struct Row2;
18681
18682        struct Row4;
18683        struct Row5;
18684        struct Row6;
18685
18686        struct Row8;
18687        struct Row9;
18688        struct Row10;"#};
18689
18690    // Modification hunks behave the same as the addition ones.
18691    assert_hunk_revert(
18692        indoc! {r#"struct Row;
18693                   struct Row1;
18694                   struct Row33;
18695                   ˇ
18696                   struct Row4;
18697                   struct Row5;
18698                   struct Row6;
18699                   ˇ
18700                   struct Row99;
18701                   struct Row9;
18702                   struct Row10;"#},
18703        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18704        indoc! {r#"struct Row;
18705                   struct Row1;
18706                   struct Row33;
18707                   ˇ
18708                   struct Row4;
18709                   struct Row5;
18710                   struct Row6;
18711                   ˇ
18712                   struct Row99;
18713                   struct Row9;
18714                   struct Row10;"#},
18715        base_text,
18716        &mut cx,
18717    );
18718    assert_hunk_revert(
18719        indoc! {r#"struct Row;
18720                   struct Row1;
18721                   struct Row33;
18722                   «ˇ
18723                   struct Row4;
18724                   struct» Row5;
18725                   «struct Row6;
18726                   ˇ»
18727                   struct Row99;
18728                   struct Row9;
18729                   struct Row10;"#},
18730        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18731        indoc! {r#"struct Row;
18732                   struct Row1;
18733                   struct Row33;
18734                   «ˇ
18735                   struct Row4;
18736                   struct» Row5;
18737                   «struct Row6;
18738                   ˇ»
18739                   struct Row99;
18740                   struct Row9;
18741                   struct Row10;"#},
18742        base_text,
18743        &mut cx,
18744    );
18745
18746    assert_hunk_revert(
18747        indoc! {r#"ˇstruct Row1.1;
18748                   struct Row1;
18749                   «ˇstr»uct Row22;
18750
18751                   struct ˇRow44;
18752                   struct Row5;
18753                   struct «Rˇ»ow66;ˇ
18754
18755                   «struˇ»ct Row88;
18756                   struct Row9;
18757                   struct Row1011;ˇ"#},
18758        vec![
18759            DiffHunkStatusKind::Modified,
18760            DiffHunkStatusKind::Modified,
18761            DiffHunkStatusKind::Modified,
18762            DiffHunkStatusKind::Modified,
18763            DiffHunkStatusKind::Modified,
18764            DiffHunkStatusKind::Modified,
18765        ],
18766        indoc! {r#"struct Row;
18767                   ˇstruct Row1;
18768                   struct Row2;
18769                   ˇ
18770                   struct Row4;
18771                   ˇstruct Row5;
18772                   struct Row6;
18773                   ˇ
18774                   struct Row8;
18775                   ˇstruct Row9;
18776                   struct Row10;ˇ"#},
18777        base_text,
18778        &mut cx,
18779    );
18780}
18781
18782#[gpui::test]
18783async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18784    init_test(cx, |_| {});
18785    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18786    let base_text = indoc! {r#"
18787        one
18788
18789        two
18790        three
18791        "#};
18792
18793    cx.set_head_text(base_text);
18794    cx.set_state("\nˇ\n");
18795    cx.executor().run_until_parked();
18796    cx.update_editor(|editor, _window, cx| {
18797        editor.expand_selected_diff_hunks(cx);
18798    });
18799    cx.executor().run_until_parked();
18800    cx.update_editor(|editor, window, cx| {
18801        editor.backspace(&Default::default(), window, cx);
18802    });
18803    cx.run_until_parked();
18804    cx.assert_state_with_diff(
18805        indoc! {r#"
18806
18807        - two
18808        - threeˇ
18809        +
18810        "#}
18811        .to_string(),
18812    );
18813}
18814
18815#[gpui::test]
18816async fn test_deletion_reverts(cx: &mut TestAppContext) {
18817    init_test(cx, |_| {});
18818    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18819    let base_text = indoc! {r#"struct Row;
18820struct Row1;
18821struct Row2;
18822
18823struct Row4;
18824struct Row5;
18825struct Row6;
18826
18827struct Row8;
18828struct Row9;
18829struct Row10;"#};
18830
18831    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18832    assert_hunk_revert(
18833        indoc! {r#"struct Row;
18834                   struct Row2;
18835
18836                   ˇstruct Row4;
18837                   struct Row5;
18838                   struct Row6;
18839                   ˇ
18840                   struct Row8;
18841                   struct Row10;"#},
18842        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18843        indoc! {r#"struct Row;
18844                   struct Row2;
18845
18846                   ˇstruct Row4;
18847                   struct Row5;
18848                   struct Row6;
18849                   ˇ
18850                   struct Row8;
18851                   struct Row10;"#},
18852        base_text,
18853        &mut cx,
18854    );
18855    assert_hunk_revert(
18856        indoc! {r#"struct Row;
18857                   struct Row2;
18858
18859                   «ˇstruct Row4;
18860                   struct» Row5;
18861                   «struct Row6;
18862                   ˇ»
18863                   struct Row8;
18864                   struct Row10;"#},
18865        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18866        indoc! {r#"struct Row;
18867                   struct Row2;
18868
18869                   «ˇstruct Row4;
18870                   struct» Row5;
18871                   «struct Row6;
18872                   ˇ»
18873                   struct Row8;
18874                   struct Row10;"#},
18875        base_text,
18876        &mut cx,
18877    );
18878
18879    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18880    assert_hunk_revert(
18881        indoc! {r#"struct Row;
18882                   ˇstruct Row2;
18883
18884                   struct Row4;
18885                   struct Row5;
18886                   struct Row6;
18887
18888                   struct Row8;ˇ
18889                   struct Row10;"#},
18890        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18891        indoc! {r#"struct Row;
18892                   struct Row1;
18893                   ˇstruct Row2;
18894
18895                   struct Row4;
18896                   struct Row5;
18897                   struct Row6;
18898
18899                   struct Row8;ˇ
18900                   struct Row9;
18901                   struct Row10;"#},
18902        base_text,
18903        &mut cx,
18904    );
18905    assert_hunk_revert(
18906        indoc! {r#"struct Row;
18907                   struct Row2«ˇ;
18908                   struct Row4;
18909                   struct» Row5;
18910                   «struct Row6;
18911
18912                   struct Row8;ˇ»
18913                   struct Row10;"#},
18914        vec![
18915            DiffHunkStatusKind::Deleted,
18916            DiffHunkStatusKind::Deleted,
18917            DiffHunkStatusKind::Deleted,
18918        ],
18919        indoc! {r#"struct Row;
18920                   struct Row1;
18921                   struct Row2«ˇ;
18922
18923                   struct Row4;
18924                   struct» Row5;
18925                   «struct Row6;
18926
18927                   struct Row8;ˇ»
18928                   struct Row9;
18929                   struct Row10;"#},
18930        base_text,
18931        &mut cx,
18932    );
18933}
18934
18935#[gpui::test]
18936async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18937    init_test(cx, |_| {});
18938
18939    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18940    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18941    let base_text_3 =
18942        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18943
18944    let text_1 = edit_first_char_of_every_line(base_text_1);
18945    let text_2 = edit_first_char_of_every_line(base_text_2);
18946    let text_3 = edit_first_char_of_every_line(base_text_3);
18947
18948    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18949    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18950    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18951
18952    let multibuffer = cx.new(|cx| {
18953        let mut multibuffer = MultiBuffer::new(ReadWrite);
18954        multibuffer.push_excerpts(
18955            buffer_1.clone(),
18956            [
18957                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18958                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18959                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18960            ],
18961            cx,
18962        );
18963        multibuffer.push_excerpts(
18964            buffer_2.clone(),
18965            [
18966                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18967                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18968                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18969            ],
18970            cx,
18971        );
18972        multibuffer.push_excerpts(
18973            buffer_3.clone(),
18974            [
18975                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18976                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18977                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18978            ],
18979            cx,
18980        );
18981        multibuffer
18982    });
18983
18984    let fs = FakeFs::new(cx.executor());
18985    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18986    let (editor, cx) = cx
18987        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18988    editor.update_in(cx, |editor, _window, cx| {
18989        for (buffer, diff_base) in [
18990            (buffer_1.clone(), base_text_1),
18991            (buffer_2.clone(), base_text_2),
18992            (buffer_3.clone(), base_text_3),
18993        ] {
18994            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18995            editor
18996                .buffer
18997                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18998        }
18999    });
19000    cx.executor().run_until_parked();
19001
19002    editor.update_in(cx, |editor, window, cx| {
19003        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}");
19004        editor.select_all(&SelectAll, window, cx);
19005        editor.git_restore(&Default::default(), window, cx);
19006    });
19007    cx.executor().run_until_parked();
19008
19009    // When all ranges are selected, all buffer hunks are reverted.
19010    editor.update(cx, |editor, cx| {
19011        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");
19012    });
19013    buffer_1.update(cx, |buffer, _| {
19014        assert_eq!(buffer.text(), base_text_1);
19015    });
19016    buffer_2.update(cx, |buffer, _| {
19017        assert_eq!(buffer.text(), base_text_2);
19018    });
19019    buffer_3.update(cx, |buffer, _| {
19020        assert_eq!(buffer.text(), base_text_3);
19021    });
19022
19023    editor.update_in(cx, |editor, window, cx| {
19024        editor.undo(&Default::default(), window, cx);
19025    });
19026
19027    editor.update_in(cx, |editor, window, cx| {
19028        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19029            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19030        });
19031        editor.git_restore(&Default::default(), window, cx);
19032    });
19033
19034    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19035    // but not affect buffer_2 and its related excerpts.
19036    editor.update(cx, |editor, cx| {
19037        assert_eq!(
19038            editor.text(cx),
19039            "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}"
19040        );
19041    });
19042    buffer_1.update(cx, |buffer, _| {
19043        assert_eq!(buffer.text(), base_text_1);
19044    });
19045    buffer_2.update(cx, |buffer, _| {
19046        assert_eq!(
19047            buffer.text(),
19048            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19049        );
19050    });
19051    buffer_3.update(cx, |buffer, _| {
19052        assert_eq!(
19053            buffer.text(),
19054            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19055        );
19056    });
19057
19058    fn edit_first_char_of_every_line(text: &str) -> String {
19059        text.split('\n')
19060            .map(|line| format!("X{}", &line[1..]))
19061            .collect::<Vec<_>>()
19062            .join("\n")
19063    }
19064}
19065
19066#[gpui::test]
19067async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19068    init_test(cx, |_| {});
19069
19070    let cols = 4;
19071    let rows = 10;
19072    let sample_text_1 = sample_text(rows, cols, 'a');
19073    assert_eq!(
19074        sample_text_1,
19075        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19076    );
19077    let sample_text_2 = sample_text(rows, cols, 'l');
19078    assert_eq!(
19079        sample_text_2,
19080        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19081    );
19082    let sample_text_3 = sample_text(rows, cols, 'v');
19083    assert_eq!(
19084        sample_text_3,
19085        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19086    );
19087
19088    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19089    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19090    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19091
19092    let multi_buffer = cx.new(|cx| {
19093        let mut multibuffer = MultiBuffer::new(ReadWrite);
19094        multibuffer.push_excerpts(
19095            buffer_1.clone(),
19096            [
19097                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19098                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19099                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19100            ],
19101            cx,
19102        );
19103        multibuffer.push_excerpts(
19104            buffer_2.clone(),
19105            [
19106                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19107                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19108                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19109            ],
19110            cx,
19111        );
19112        multibuffer.push_excerpts(
19113            buffer_3.clone(),
19114            [
19115                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19116                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19117                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19118            ],
19119            cx,
19120        );
19121        multibuffer
19122    });
19123
19124    let fs = FakeFs::new(cx.executor());
19125    fs.insert_tree(
19126        "/a",
19127        json!({
19128            "main.rs": sample_text_1,
19129            "other.rs": sample_text_2,
19130            "lib.rs": sample_text_3,
19131        }),
19132    )
19133    .await;
19134    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19135    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19136    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19137    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19138        Editor::new(
19139            EditorMode::full(),
19140            multi_buffer,
19141            Some(project.clone()),
19142            window,
19143            cx,
19144        )
19145    });
19146    let multibuffer_item_id = workspace
19147        .update(cx, |workspace, window, cx| {
19148            assert!(
19149                workspace.active_item(cx).is_none(),
19150                "active item should be None before the first item is added"
19151            );
19152            workspace.add_item_to_active_pane(
19153                Box::new(multi_buffer_editor.clone()),
19154                None,
19155                true,
19156                window,
19157                cx,
19158            );
19159            let active_item = workspace
19160                .active_item(cx)
19161                .expect("should have an active item after adding the multi buffer");
19162            assert_eq!(
19163                active_item.buffer_kind(cx),
19164                ItemBufferKind::Multibuffer,
19165                "A multi buffer was expected to active after adding"
19166            );
19167            active_item.item_id()
19168        })
19169        .unwrap();
19170    cx.executor().run_until_parked();
19171
19172    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19173        editor.change_selections(
19174            SelectionEffects::scroll(Autoscroll::Next),
19175            window,
19176            cx,
19177            |s| s.select_ranges(Some(1..2)),
19178        );
19179        editor.open_excerpts(&OpenExcerpts, window, cx);
19180    });
19181    cx.executor().run_until_parked();
19182    let first_item_id = workspace
19183        .update(cx, |workspace, window, cx| {
19184            let active_item = workspace
19185                .active_item(cx)
19186                .expect("should have an active item after navigating into the 1st buffer");
19187            let first_item_id = active_item.item_id();
19188            assert_ne!(
19189                first_item_id, multibuffer_item_id,
19190                "Should navigate into the 1st buffer and activate it"
19191            );
19192            assert_eq!(
19193                active_item.buffer_kind(cx),
19194                ItemBufferKind::Singleton,
19195                "New active item should be a singleton buffer"
19196            );
19197            assert_eq!(
19198                active_item
19199                    .act_as::<Editor>(cx)
19200                    .expect("should have navigated into an editor for the 1st buffer")
19201                    .read(cx)
19202                    .text(cx),
19203                sample_text_1
19204            );
19205
19206            workspace
19207                .go_back(workspace.active_pane().downgrade(), window, cx)
19208                .detach_and_log_err(cx);
19209
19210            first_item_id
19211        })
19212        .unwrap();
19213    cx.executor().run_until_parked();
19214    workspace
19215        .update(cx, |workspace, _, cx| {
19216            let active_item = workspace
19217                .active_item(cx)
19218                .expect("should have an active item after navigating back");
19219            assert_eq!(
19220                active_item.item_id(),
19221                multibuffer_item_id,
19222                "Should navigate back to the multi buffer"
19223            );
19224            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19225        })
19226        .unwrap();
19227
19228    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19229        editor.change_selections(
19230            SelectionEffects::scroll(Autoscroll::Next),
19231            window,
19232            cx,
19233            |s| s.select_ranges(Some(39..40)),
19234        );
19235        editor.open_excerpts(&OpenExcerpts, window, cx);
19236    });
19237    cx.executor().run_until_parked();
19238    let second_item_id = workspace
19239        .update(cx, |workspace, window, cx| {
19240            let active_item = workspace
19241                .active_item(cx)
19242                .expect("should have an active item after navigating into the 2nd buffer");
19243            let second_item_id = active_item.item_id();
19244            assert_ne!(
19245                second_item_id, multibuffer_item_id,
19246                "Should navigate away from the multibuffer"
19247            );
19248            assert_ne!(
19249                second_item_id, first_item_id,
19250                "Should navigate into the 2nd buffer and activate it"
19251            );
19252            assert_eq!(
19253                active_item.buffer_kind(cx),
19254                ItemBufferKind::Singleton,
19255                "New active item should be a singleton buffer"
19256            );
19257            assert_eq!(
19258                active_item
19259                    .act_as::<Editor>(cx)
19260                    .expect("should have navigated into an editor")
19261                    .read(cx)
19262                    .text(cx),
19263                sample_text_2
19264            );
19265
19266            workspace
19267                .go_back(workspace.active_pane().downgrade(), window, cx)
19268                .detach_and_log_err(cx);
19269
19270            second_item_id
19271        })
19272        .unwrap();
19273    cx.executor().run_until_parked();
19274    workspace
19275        .update(cx, |workspace, _, cx| {
19276            let active_item = workspace
19277                .active_item(cx)
19278                .expect("should have an active item after navigating back from the 2nd buffer");
19279            assert_eq!(
19280                active_item.item_id(),
19281                multibuffer_item_id,
19282                "Should navigate back from the 2nd buffer to the multi buffer"
19283            );
19284            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19285        })
19286        .unwrap();
19287
19288    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19289        editor.change_selections(
19290            SelectionEffects::scroll(Autoscroll::Next),
19291            window,
19292            cx,
19293            |s| s.select_ranges(Some(70..70)),
19294        );
19295        editor.open_excerpts(&OpenExcerpts, window, cx);
19296    });
19297    cx.executor().run_until_parked();
19298    workspace
19299        .update(cx, |workspace, window, cx| {
19300            let active_item = workspace
19301                .active_item(cx)
19302                .expect("should have an active item after navigating into the 3rd buffer");
19303            let third_item_id = active_item.item_id();
19304            assert_ne!(
19305                third_item_id, multibuffer_item_id,
19306                "Should navigate into the 3rd buffer and activate it"
19307            );
19308            assert_ne!(third_item_id, first_item_id);
19309            assert_ne!(third_item_id, second_item_id);
19310            assert_eq!(
19311                active_item.buffer_kind(cx),
19312                ItemBufferKind::Singleton,
19313                "New active item should be a singleton buffer"
19314            );
19315            assert_eq!(
19316                active_item
19317                    .act_as::<Editor>(cx)
19318                    .expect("should have navigated into an editor")
19319                    .read(cx)
19320                    .text(cx),
19321                sample_text_3
19322            );
19323
19324            workspace
19325                .go_back(workspace.active_pane().downgrade(), window, cx)
19326                .detach_and_log_err(cx);
19327        })
19328        .unwrap();
19329    cx.executor().run_until_parked();
19330    workspace
19331        .update(cx, |workspace, _, cx| {
19332            let active_item = workspace
19333                .active_item(cx)
19334                .expect("should have an active item after navigating back from the 3rd buffer");
19335            assert_eq!(
19336                active_item.item_id(),
19337                multibuffer_item_id,
19338                "Should navigate back from the 3rd buffer to the multi buffer"
19339            );
19340            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19341        })
19342        .unwrap();
19343}
19344
19345#[gpui::test]
19346async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19347    init_test(cx, |_| {});
19348
19349    let mut cx = EditorTestContext::new(cx).await;
19350
19351    let diff_base = r#"
19352        use some::mod;
19353
19354        const A: u32 = 42;
19355
19356        fn main() {
19357            println!("hello");
19358
19359            println!("world");
19360        }
19361        "#
19362    .unindent();
19363
19364    cx.set_state(
19365        &r#"
19366        use some::modified;
19367
19368        ˇ
19369        fn main() {
19370            println!("hello there");
19371
19372            println!("around the");
19373            println!("world");
19374        }
19375        "#
19376        .unindent(),
19377    );
19378
19379    cx.set_head_text(&diff_base);
19380    executor.run_until_parked();
19381
19382    cx.update_editor(|editor, window, cx| {
19383        editor.go_to_next_hunk(&GoToHunk, window, cx);
19384        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19385    });
19386    executor.run_until_parked();
19387    cx.assert_state_with_diff(
19388        r#"
19389          use some::modified;
19390
19391
19392          fn main() {
19393        -     println!("hello");
19394        + ˇ    println!("hello there");
19395
19396              println!("around the");
19397              println!("world");
19398          }
19399        "#
19400        .unindent(),
19401    );
19402
19403    cx.update_editor(|editor, window, cx| {
19404        for _ in 0..2 {
19405            editor.go_to_next_hunk(&GoToHunk, window, cx);
19406            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19407        }
19408    });
19409    executor.run_until_parked();
19410    cx.assert_state_with_diff(
19411        r#"
19412        - use some::mod;
19413        + ˇuse some::modified;
19414
19415
19416          fn main() {
19417        -     println!("hello");
19418        +     println!("hello there");
19419
19420        +     println!("around the");
19421              println!("world");
19422          }
19423        "#
19424        .unindent(),
19425    );
19426
19427    cx.update_editor(|editor, window, cx| {
19428        editor.go_to_next_hunk(&GoToHunk, window, cx);
19429        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19430    });
19431    executor.run_until_parked();
19432    cx.assert_state_with_diff(
19433        r#"
19434        - use some::mod;
19435        + use some::modified;
19436
19437        - const A: u32 = 42;
19438          ˇ
19439          fn main() {
19440        -     println!("hello");
19441        +     println!("hello there");
19442
19443        +     println!("around the");
19444              println!("world");
19445          }
19446        "#
19447        .unindent(),
19448    );
19449
19450    cx.update_editor(|editor, window, cx| {
19451        editor.cancel(&Cancel, window, cx);
19452    });
19453
19454    cx.assert_state_with_diff(
19455        r#"
19456          use some::modified;
19457
19458          ˇ
19459          fn main() {
19460              println!("hello there");
19461
19462              println!("around the");
19463              println!("world");
19464          }
19465        "#
19466        .unindent(),
19467    );
19468}
19469
19470#[gpui::test]
19471async fn test_diff_base_change_with_expanded_diff_hunks(
19472    executor: BackgroundExecutor,
19473    cx: &mut TestAppContext,
19474) {
19475    init_test(cx, |_| {});
19476
19477    let mut cx = EditorTestContext::new(cx).await;
19478
19479    let diff_base = r#"
19480        use some::mod1;
19481        use some::mod2;
19482
19483        const A: u32 = 42;
19484        const B: u32 = 42;
19485        const C: u32 = 42;
19486
19487        fn main() {
19488            println!("hello");
19489
19490            println!("world");
19491        }
19492        "#
19493    .unindent();
19494
19495    cx.set_state(
19496        &r#"
19497        use some::mod2;
19498
19499        const A: u32 = 42;
19500        const C: u32 = 42;
19501
19502        fn main(ˇ) {
19503            //println!("hello");
19504
19505            println!("world");
19506            //
19507            //
19508        }
19509        "#
19510        .unindent(),
19511    );
19512
19513    cx.set_head_text(&diff_base);
19514    executor.run_until_parked();
19515
19516    cx.update_editor(|editor, window, cx| {
19517        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19518    });
19519    executor.run_until_parked();
19520    cx.assert_state_with_diff(
19521        r#"
19522        - use some::mod1;
19523          use some::mod2;
19524
19525          const A: u32 = 42;
19526        - const B: u32 = 42;
19527          const C: u32 = 42;
19528
19529          fn main(ˇ) {
19530        -     println!("hello");
19531        +     //println!("hello");
19532
19533              println!("world");
19534        +     //
19535        +     //
19536          }
19537        "#
19538        .unindent(),
19539    );
19540
19541    cx.set_head_text("new diff base!");
19542    executor.run_until_parked();
19543    cx.assert_state_with_diff(
19544        r#"
19545        - new diff base!
19546        + use some::mod2;
19547        +
19548        + const A: u32 = 42;
19549        + const C: u32 = 42;
19550        +
19551        + fn main(ˇ) {
19552        +     //println!("hello");
19553        +
19554        +     println!("world");
19555        +     //
19556        +     //
19557        + }
19558        "#
19559        .unindent(),
19560    );
19561}
19562
19563#[gpui::test]
19564async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19565    init_test(cx, |_| {});
19566
19567    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19568    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19569    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19570    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19571    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19572    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19573
19574    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19575    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19576    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19577
19578    let multi_buffer = cx.new(|cx| {
19579        let mut multibuffer = MultiBuffer::new(ReadWrite);
19580        multibuffer.push_excerpts(
19581            buffer_1.clone(),
19582            [
19583                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19584                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19585                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19586            ],
19587            cx,
19588        );
19589        multibuffer.push_excerpts(
19590            buffer_2.clone(),
19591            [
19592                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19593                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19594                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19595            ],
19596            cx,
19597        );
19598        multibuffer.push_excerpts(
19599            buffer_3.clone(),
19600            [
19601                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19602                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19603                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19604            ],
19605            cx,
19606        );
19607        multibuffer
19608    });
19609
19610    let editor =
19611        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19612    editor
19613        .update(cx, |editor, _window, cx| {
19614            for (buffer, diff_base) in [
19615                (buffer_1.clone(), file_1_old),
19616                (buffer_2.clone(), file_2_old),
19617                (buffer_3.clone(), file_3_old),
19618            ] {
19619                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19620                editor
19621                    .buffer
19622                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19623            }
19624        })
19625        .unwrap();
19626
19627    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19628    cx.run_until_parked();
19629
19630    cx.assert_editor_state(
19631        &"
19632            ˇaaa
19633            ccc
19634            ddd
19635
19636            ggg
19637            hhh
19638
19639
19640            lll
19641            mmm
19642            NNN
19643
19644            qqq
19645            rrr
19646
19647            uuu
19648            111
19649            222
19650            333
19651
19652            666
19653            777
19654
19655            000
19656            !!!"
19657        .unindent(),
19658    );
19659
19660    cx.update_editor(|editor, window, cx| {
19661        editor.select_all(&SelectAll, window, cx);
19662        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19663    });
19664    cx.executor().run_until_parked();
19665
19666    cx.assert_state_with_diff(
19667        "
19668            «aaa
19669          - bbb
19670            ccc
19671            ddd
19672
19673            ggg
19674            hhh
19675
19676
19677            lll
19678            mmm
19679          - nnn
19680          + NNN
19681
19682            qqq
19683            rrr
19684
19685            uuu
19686            111
19687            222
19688            333
19689
19690          + 666
19691            777
19692
19693            000
19694            !!!ˇ»"
19695            .unindent(),
19696    );
19697}
19698
19699#[gpui::test]
19700async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19701    init_test(cx, |_| {});
19702
19703    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19704    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19705
19706    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19707    let multi_buffer = cx.new(|cx| {
19708        let mut multibuffer = MultiBuffer::new(ReadWrite);
19709        multibuffer.push_excerpts(
19710            buffer.clone(),
19711            [
19712                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19713                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19714                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19715            ],
19716            cx,
19717        );
19718        multibuffer
19719    });
19720
19721    let editor =
19722        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19723    editor
19724        .update(cx, |editor, _window, cx| {
19725            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19726            editor
19727                .buffer
19728                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19729        })
19730        .unwrap();
19731
19732    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19733    cx.run_until_parked();
19734
19735    cx.update_editor(|editor, window, cx| {
19736        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19737    });
19738    cx.executor().run_until_parked();
19739
19740    // When the start of a hunk coincides with the start of its excerpt,
19741    // the hunk is expanded. When the start of a hunk is earlier than
19742    // the start of its excerpt, the hunk is not expanded.
19743    cx.assert_state_with_diff(
19744        "
19745            ˇaaa
19746          - bbb
19747          + BBB
19748
19749          - ddd
19750          - eee
19751          + DDD
19752          + EEE
19753            fff
19754
19755            iii
19756        "
19757        .unindent(),
19758    );
19759}
19760
19761#[gpui::test]
19762async fn test_edits_around_expanded_insertion_hunks(
19763    executor: BackgroundExecutor,
19764    cx: &mut TestAppContext,
19765) {
19766    init_test(cx, |_| {});
19767
19768    let mut cx = EditorTestContext::new(cx).await;
19769
19770    let diff_base = r#"
19771        use some::mod1;
19772        use some::mod2;
19773
19774        const A: u32 = 42;
19775
19776        fn main() {
19777            println!("hello");
19778
19779            println!("world");
19780        }
19781        "#
19782    .unindent();
19783    executor.run_until_parked();
19784    cx.set_state(
19785        &r#"
19786        use some::mod1;
19787        use some::mod2;
19788
19789        const A: u32 = 42;
19790        const B: u32 = 42;
19791        const C: u32 = 42;
19792        ˇ
19793
19794        fn main() {
19795            println!("hello");
19796
19797            println!("world");
19798        }
19799        "#
19800        .unindent(),
19801    );
19802
19803    cx.set_head_text(&diff_base);
19804    executor.run_until_parked();
19805
19806    cx.update_editor(|editor, window, cx| {
19807        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19808    });
19809    executor.run_until_parked();
19810
19811    cx.assert_state_with_diff(
19812        r#"
19813        use some::mod1;
19814        use some::mod2;
19815
19816        const A: u32 = 42;
19817      + const B: u32 = 42;
19818      + const C: u32 = 42;
19819      + ˇ
19820
19821        fn main() {
19822            println!("hello");
19823
19824            println!("world");
19825        }
19826      "#
19827        .unindent(),
19828    );
19829
19830    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19831    executor.run_until_parked();
19832
19833    cx.assert_state_with_diff(
19834        r#"
19835        use some::mod1;
19836        use some::mod2;
19837
19838        const A: u32 = 42;
19839      + const B: u32 = 42;
19840      + const C: u32 = 42;
19841      + const D: u32 = 42;
19842      + ˇ
19843
19844        fn main() {
19845            println!("hello");
19846
19847            println!("world");
19848        }
19849      "#
19850        .unindent(),
19851    );
19852
19853    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19854    executor.run_until_parked();
19855
19856    cx.assert_state_with_diff(
19857        r#"
19858        use some::mod1;
19859        use some::mod2;
19860
19861        const A: u32 = 42;
19862      + const B: u32 = 42;
19863      + const C: u32 = 42;
19864      + const D: u32 = 42;
19865      + const E: u32 = 42;
19866      + ˇ
19867
19868        fn main() {
19869            println!("hello");
19870
19871            println!("world");
19872        }
19873      "#
19874        .unindent(),
19875    );
19876
19877    cx.update_editor(|editor, window, cx| {
19878        editor.delete_line(&DeleteLine, window, cx);
19879    });
19880    executor.run_until_parked();
19881
19882    cx.assert_state_with_diff(
19883        r#"
19884        use some::mod1;
19885        use some::mod2;
19886
19887        const A: u32 = 42;
19888      + const B: u32 = 42;
19889      + const C: u32 = 42;
19890      + const D: u32 = 42;
19891      + const E: u32 = 42;
19892        ˇ
19893        fn main() {
19894            println!("hello");
19895
19896            println!("world");
19897        }
19898      "#
19899        .unindent(),
19900    );
19901
19902    cx.update_editor(|editor, window, cx| {
19903        editor.move_up(&MoveUp, window, cx);
19904        editor.delete_line(&DeleteLine, window, cx);
19905        editor.move_up(&MoveUp, window, cx);
19906        editor.delete_line(&DeleteLine, window, cx);
19907        editor.move_up(&MoveUp, window, cx);
19908        editor.delete_line(&DeleteLine, window, cx);
19909    });
19910    executor.run_until_parked();
19911    cx.assert_state_with_diff(
19912        r#"
19913        use some::mod1;
19914        use some::mod2;
19915
19916        const A: u32 = 42;
19917      + const B: u32 = 42;
19918        ˇ
19919        fn main() {
19920            println!("hello");
19921
19922            println!("world");
19923        }
19924      "#
19925        .unindent(),
19926    );
19927
19928    cx.update_editor(|editor, window, cx| {
19929        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19930        editor.delete_line(&DeleteLine, window, cx);
19931    });
19932    executor.run_until_parked();
19933    cx.assert_state_with_diff(
19934        r#"
19935        ˇ
19936        fn main() {
19937            println!("hello");
19938
19939            println!("world");
19940        }
19941      "#
19942        .unindent(),
19943    );
19944}
19945
19946#[gpui::test]
19947async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19948    init_test(cx, |_| {});
19949
19950    let mut cx = EditorTestContext::new(cx).await;
19951    cx.set_head_text(indoc! { "
19952        one
19953        two
19954        three
19955        four
19956        five
19957        "
19958    });
19959    cx.set_state(indoc! { "
19960        one
19961        ˇthree
19962        five
19963    "});
19964    cx.run_until_parked();
19965    cx.update_editor(|editor, window, cx| {
19966        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19967    });
19968    cx.assert_state_with_diff(
19969        indoc! { "
19970        one
19971      - two
19972        ˇthree
19973      - four
19974        five
19975    "}
19976        .to_string(),
19977    );
19978    cx.update_editor(|editor, window, cx| {
19979        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19980    });
19981
19982    cx.assert_state_with_diff(
19983        indoc! { "
19984        one
19985        ˇthree
19986        five
19987    "}
19988        .to_string(),
19989    );
19990
19991    cx.set_state(indoc! { "
19992        one
19993        ˇTWO
19994        three
19995        four
19996        five
19997    "});
19998    cx.run_until_parked();
19999    cx.update_editor(|editor, window, cx| {
20000        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20001    });
20002
20003    cx.assert_state_with_diff(
20004        indoc! { "
20005            one
20006          - two
20007          + ˇTWO
20008            three
20009            four
20010            five
20011        "}
20012        .to_string(),
20013    );
20014    cx.update_editor(|editor, window, cx| {
20015        editor.move_up(&Default::default(), window, cx);
20016        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20017    });
20018    cx.assert_state_with_diff(
20019        indoc! { "
20020            one
20021            ˇTWO
20022            three
20023            four
20024            five
20025        "}
20026        .to_string(),
20027    );
20028}
20029
20030#[gpui::test]
20031async fn test_edits_around_expanded_deletion_hunks(
20032    executor: BackgroundExecutor,
20033    cx: &mut TestAppContext,
20034) {
20035    init_test(cx, |_| {});
20036
20037    let mut cx = EditorTestContext::new(cx).await;
20038
20039    let diff_base = r#"
20040        use some::mod1;
20041        use some::mod2;
20042
20043        const A: u32 = 42;
20044        const B: u32 = 42;
20045        const C: u32 = 42;
20046
20047
20048        fn main() {
20049            println!("hello");
20050
20051            println!("world");
20052        }
20053    "#
20054    .unindent();
20055    executor.run_until_parked();
20056    cx.set_state(
20057        &r#"
20058        use some::mod1;
20059        use some::mod2;
20060
20061        ˇconst B: u32 = 42;
20062        const C: u32 = 42;
20063
20064
20065        fn main() {
20066            println!("hello");
20067
20068            println!("world");
20069        }
20070        "#
20071        .unindent(),
20072    );
20073
20074    cx.set_head_text(&diff_base);
20075    executor.run_until_parked();
20076
20077    cx.update_editor(|editor, window, cx| {
20078        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20079    });
20080    executor.run_until_parked();
20081
20082    cx.assert_state_with_diff(
20083        r#"
20084        use some::mod1;
20085        use some::mod2;
20086
20087      - const A: u32 = 42;
20088        ˇconst B: u32 = 42;
20089        const C: u32 = 42;
20090
20091
20092        fn main() {
20093            println!("hello");
20094
20095            println!("world");
20096        }
20097      "#
20098        .unindent(),
20099    );
20100
20101    cx.update_editor(|editor, window, cx| {
20102        editor.delete_line(&DeleteLine, window, cx);
20103    });
20104    executor.run_until_parked();
20105    cx.assert_state_with_diff(
20106        r#"
20107        use some::mod1;
20108        use some::mod2;
20109
20110      - const A: u32 = 42;
20111      - const B: u32 = 42;
20112        ˇconst C: u32 = 42;
20113
20114
20115        fn main() {
20116            println!("hello");
20117
20118            println!("world");
20119        }
20120      "#
20121        .unindent(),
20122    );
20123
20124    cx.update_editor(|editor, window, cx| {
20125        editor.delete_line(&DeleteLine, window, cx);
20126    });
20127    executor.run_until_parked();
20128    cx.assert_state_with_diff(
20129        r#"
20130        use some::mod1;
20131        use some::mod2;
20132
20133      - const A: u32 = 42;
20134      - const B: u32 = 42;
20135      - const C: u32 = 42;
20136        ˇ
20137
20138        fn main() {
20139            println!("hello");
20140
20141            println!("world");
20142        }
20143      "#
20144        .unindent(),
20145    );
20146
20147    cx.update_editor(|editor, window, cx| {
20148        editor.handle_input("replacement", window, cx);
20149    });
20150    executor.run_until_parked();
20151    cx.assert_state_with_diff(
20152        r#"
20153        use some::mod1;
20154        use some::mod2;
20155
20156      - const A: u32 = 42;
20157      - const B: u32 = 42;
20158      - const C: u32 = 42;
20159      -
20160      + replacementˇ
20161
20162        fn main() {
20163            println!("hello");
20164
20165            println!("world");
20166        }
20167      "#
20168        .unindent(),
20169    );
20170}
20171
20172#[gpui::test]
20173async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20174    init_test(cx, |_| {});
20175
20176    let mut cx = EditorTestContext::new(cx).await;
20177
20178    let base_text = r#"
20179        one
20180        two
20181        three
20182        four
20183        five
20184    "#
20185    .unindent();
20186    executor.run_until_parked();
20187    cx.set_state(
20188        &r#"
20189        one
20190        two
20191        fˇour
20192        five
20193        "#
20194        .unindent(),
20195    );
20196
20197    cx.set_head_text(&base_text);
20198    executor.run_until_parked();
20199
20200    cx.update_editor(|editor, window, cx| {
20201        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20202    });
20203    executor.run_until_parked();
20204
20205    cx.assert_state_with_diff(
20206        r#"
20207          one
20208          two
20209        - three
20210          fˇour
20211          five
20212        "#
20213        .unindent(),
20214    );
20215
20216    cx.update_editor(|editor, window, cx| {
20217        editor.backspace(&Backspace, window, cx);
20218        editor.backspace(&Backspace, window, cx);
20219    });
20220    executor.run_until_parked();
20221    cx.assert_state_with_diff(
20222        r#"
20223          one
20224          two
20225        - threeˇ
20226        - four
20227        + our
20228          five
20229        "#
20230        .unindent(),
20231    );
20232}
20233
20234#[gpui::test]
20235async fn test_edit_after_expanded_modification_hunk(
20236    executor: BackgroundExecutor,
20237    cx: &mut TestAppContext,
20238) {
20239    init_test(cx, |_| {});
20240
20241    let mut cx = EditorTestContext::new(cx).await;
20242
20243    let diff_base = r#"
20244        use some::mod1;
20245        use some::mod2;
20246
20247        const A: u32 = 42;
20248        const B: u32 = 42;
20249        const C: u32 = 42;
20250        const D: u32 = 42;
20251
20252
20253        fn main() {
20254            println!("hello");
20255
20256            println!("world");
20257        }"#
20258    .unindent();
20259
20260    cx.set_state(
20261        &r#"
20262        use some::mod1;
20263        use some::mod2;
20264
20265        const A: u32 = 42;
20266        const B: u32 = 42;
20267        const C: u32 = 43ˇ
20268        const D: u32 = 42;
20269
20270
20271        fn main() {
20272            println!("hello");
20273
20274            println!("world");
20275        }"#
20276        .unindent(),
20277    );
20278
20279    cx.set_head_text(&diff_base);
20280    executor.run_until_parked();
20281    cx.update_editor(|editor, window, cx| {
20282        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20283    });
20284    executor.run_until_parked();
20285
20286    cx.assert_state_with_diff(
20287        r#"
20288        use some::mod1;
20289        use some::mod2;
20290
20291        const A: u32 = 42;
20292        const B: u32 = 42;
20293      - const C: u32 = 42;
20294      + const C: u32 = 43ˇ
20295        const D: u32 = 42;
20296
20297
20298        fn main() {
20299            println!("hello");
20300
20301            println!("world");
20302        }"#
20303        .unindent(),
20304    );
20305
20306    cx.update_editor(|editor, window, cx| {
20307        editor.handle_input("\nnew_line\n", window, cx);
20308    });
20309    executor.run_until_parked();
20310
20311    cx.assert_state_with_diff(
20312        r#"
20313        use some::mod1;
20314        use some::mod2;
20315
20316        const A: u32 = 42;
20317        const B: u32 = 42;
20318      - const C: u32 = 42;
20319      + const C: u32 = 43
20320      + new_line
20321      + ˇ
20322        const D: u32 = 42;
20323
20324
20325        fn main() {
20326            println!("hello");
20327
20328            println!("world");
20329        }"#
20330        .unindent(),
20331    );
20332}
20333
20334#[gpui::test]
20335async fn test_stage_and_unstage_added_file_hunk(
20336    executor: BackgroundExecutor,
20337    cx: &mut TestAppContext,
20338) {
20339    init_test(cx, |_| {});
20340
20341    let mut cx = EditorTestContext::new(cx).await;
20342    cx.update_editor(|editor, _, cx| {
20343        editor.set_expand_all_diff_hunks(cx);
20344    });
20345
20346    let working_copy = r#"
20347            ˇfn main() {
20348                println!("hello, world!");
20349            }
20350        "#
20351    .unindent();
20352
20353    cx.set_state(&working_copy);
20354    executor.run_until_parked();
20355
20356    cx.assert_state_with_diff(
20357        r#"
20358            + ˇfn main() {
20359            +     println!("hello, world!");
20360            + }
20361        "#
20362        .unindent(),
20363    );
20364    cx.assert_index_text(None);
20365
20366    cx.update_editor(|editor, window, cx| {
20367        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20368    });
20369    executor.run_until_parked();
20370    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20371    cx.assert_state_with_diff(
20372        r#"
20373            + ˇfn main() {
20374            +     println!("hello, world!");
20375            + }
20376        "#
20377        .unindent(),
20378    );
20379
20380    cx.update_editor(|editor, window, cx| {
20381        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20382    });
20383    executor.run_until_parked();
20384    cx.assert_index_text(None);
20385}
20386
20387async fn setup_indent_guides_editor(
20388    text: &str,
20389    cx: &mut TestAppContext,
20390) -> (BufferId, EditorTestContext) {
20391    init_test(cx, |_| {});
20392
20393    let mut cx = EditorTestContext::new(cx).await;
20394
20395    let buffer_id = cx.update_editor(|editor, window, cx| {
20396        editor.set_text(text, window, cx);
20397        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20398
20399        buffer_ids[0]
20400    });
20401
20402    (buffer_id, cx)
20403}
20404
20405fn assert_indent_guides(
20406    range: Range<u32>,
20407    expected: Vec<IndentGuide>,
20408    active_indices: Option<Vec<usize>>,
20409    cx: &mut EditorTestContext,
20410) {
20411    let indent_guides = cx.update_editor(|editor, window, cx| {
20412        let snapshot = editor.snapshot(window, cx).display_snapshot;
20413        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20414            editor,
20415            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20416            true,
20417            &snapshot,
20418            cx,
20419        );
20420
20421        indent_guides.sort_by(|a, b| {
20422            a.depth.cmp(&b.depth).then(
20423                a.start_row
20424                    .cmp(&b.start_row)
20425                    .then(a.end_row.cmp(&b.end_row)),
20426            )
20427        });
20428        indent_guides
20429    });
20430
20431    if let Some(expected) = active_indices {
20432        let active_indices = cx.update_editor(|editor, window, cx| {
20433            let snapshot = editor.snapshot(window, cx).display_snapshot;
20434            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20435        });
20436
20437        assert_eq!(
20438            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20439            expected,
20440            "Active indent guide indices do not match"
20441        );
20442    }
20443
20444    assert_eq!(indent_guides, expected, "Indent guides do not match");
20445}
20446
20447fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20448    IndentGuide {
20449        buffer_id,
20450        start_row: MultiBufferRow(start_row),
20451        end_row: MultiBufferRow(end_row),
20452        depth,
20453        tab_size: 4,
20454        settings: IndentGuideSettings {
20455            enabled: true,
20456            line_width: 1,
20457            active_line_width: 1,
20458            coloring: IndentGuideColoring::default(),
20459            background_coloring: IndentGuideBackgroundColoring::default(),
20460        },
20461    }
20462}
20463
20464#[gpui::test]
20465async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20466    let (buffer_id, mut cx) = setup_indent_guides_editor(
20467        &"
20468        fn main() {
20469            let a = 1;
20470        }"
20471        .unindent(),
20472        cx,
20473    )
20474    .await;
20475
20476    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20477}
20478
20479#[gpui::test]
20480async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20481    let (buffer_id, mut cx) = setup_indent_guides_editor(
20482        &"
20483        fn main() {
20484            let a = 1;
20485            let b = 2;
20486        }"
20487        .unindent(),
20488        cx,
20489    )
20490    .await;
20491
20492    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20493}
20494
20495#[gpui::test]
20496async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20497    let (buffer_id, mut cx) = setup_indent_guides_editor(
20498        &"
20499        fn main() {
20500            let a = 1;
20501            if a == 3 {
20502                let b = 2;
20503            } else {
20504                let c = 3;
20505            }
20506        }"
20507        .unindent(),
20508        cx,
20509    )
20510    .await;
20511
20512    assert_indent_guides(
20513        0..8,
20514        vec![
20515            indent_guide(buffer_id, 1, 6, 0),
20516            indent_guide(buffer_id, 3, 3, 1),
20517            indent_guide(buffer_id, 5, 5, 1),
20518        ],
20519        None,
20520        &mut cx,
20521    );
20522}
20523
20524#[gpui::test]
20525async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20526    let (buffer_id, mut cx) = setup_indent_guides_editor(
20527        &"
20528        fn main() {
20529            let a = 1;
20530                let b = 2;
20531            let c = 3;
20532        }"
20533        .unindent(),
20534        cx,
20535    )
20536    .await;
20537
20538    assert_indent_guides(
20539        0..5,
20540        vec![
20541            indent_guide(buffer_id, 1, 3, 0),
20542            indent_guide(buffer_id, 2, 2, 1),
20543        ],
20544        None,
20545        &mut cx,
20546    );
20547}
20548
20549#[gpui::test]
20550async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20551    let (buffer_id, mut cx) = setup_indent_guides_editor(
20552        &"
20553        fn main() {
20554            let a = 1;
20555
20556            let c = 3;
20557        }"
20558        .unindent(),
20559        cx,
20560    )
20561    .await;
20562
20563    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20564}
20565
20566#[gpui::test]
20567async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20568    let (buffer_id, mut cx) = setup_indent_guides_editor(
20569        &"
20570        fn main() {
20571            let a = 1;
20572
20573            let c = 3;
20574
20575            if a == 3 {
20576                let b = 2;
20577            } else {
20578                let c = 3;
20579            }
20580        }"
20581        .unindent(),
20582        cx,
20583    )
20584    .await;
20585
20586    assert_indent_guides(
20587        0..11,
20588        vec![
20589            indent_guide(buffer_id, 1, 9, 0),
20590            indent_guide(buffer_id, 6, 6, 1),
20591            indent_guide(buffer_id, 8, 8, 1),
20592        ],
20593        None,
20594        &mut cx,
20595    );
20596}
20597
20598#[gpui::test]
20599async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20600    let (buffer_id, mut cx) = setup_indent_guides_editor(
20601        &"
20602        fn main() {
20603            let a = 1;
20604
20605            let c = 3;
20606
20607            if a == 3 {
20608                let b = 2;
20609            } else {
20610                let c = 3;
20611            }
20612        }"
20613        .unindent(),
20614        cx,
20615    )
20616    .await;
20617
20618    assert_indent_guides(
20619        1..11,
20620        vec![
20621            indent_guide(buffer_id, 1, 9, 0),
20622            indent_guide(buffer_id, 6, 6, 1),
20623            indent_guide(buffer_id, 8, 8, 1),
20624        ],
20625        None,
20626        &mut cx,
20627    );
20628}
20629
20630#[gpui::test]
20631async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20632    let (buffer_id, mut cx) = setup_indent_guides_editor(
20633        &"
20634        fn main() {
20635            let a = 1;
20636
20637            let c = 3;
20638
20639            if a == 3 {
20640                let b = 2;
20641            } else {
20642                let c = 3;
20643            }
20644        }"
20645        .unindent(),
20646        cx,
20647    )
20648    .await;
20649
20650    assert_indent_guides(
20651        1..10,
20652        vec![
20653            indent_guide(buffer_id, 1, 9, 0),
20654            indent_guide(buffer_id, 6, 6, 1),
20655            indent_guide(buffer_id, 8, 8, 1),
20656        ],
20657        None,
20658        &mut cx,
20659    );
20660}
20661
20662#[gpui::test]
20663async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20664    let (buffer_id, mut cx) = setup_indent_guides_editor(
20665        &"
20666        fn main() {
20667            if a {
20668                b(
20669                    c,
20670                    d,
20671                )
20672            } else {
20673                e(
20674                    f
20675                )
20676            }
20677        }"
20678        .unindent(),
20679        cx,
20680    )
20681    .await;
20682
20683    assert_indent_guides(
20684        0..11,
20685        vec![
20686            indent_guide(buffer_id, 1, 10, 0),
20687            indent_guide(buffer_id, 2, 5, 1),
20688            indent_guide(buffer_id, 7, 9, 1),
20689            indent_guide(buffer_id, 3, 4, 2),
20690            indent_guide(buffer_id, 8, 8, 2),
20691        ],
20692        None,
20693        &mut cx,
20694    );
20695
20696    cx.update_editor(|editor, window, cx| {
20697        editor.fold_at(MultiBufferRow(2), window, cx);
20698        assert_eq!(
20699            editor.display_text(cx),
20700            "
20701            fn main() {
20702                if a {
20703                    b(⋯
20704                    )
20705                } else {
20706                    e(
20707                        f
20708                    )
20709                }
20710            }"
20711            .unindent()
20712        );
20713    });
20714
20715    assert_indent_guides(
20716        0..11,
20717        vec![
20718            indent_guide(buffer_id, 1, 10, 0),
20719            indent_guide(buffer_id, 2, 5, 1),
20720            indent_guide(buffer_id, 7, 9, 1),
20721            indent_guide(buffer_id, 8, 8, 2),
20722        ],
20723        None,
20724        &mut cx,
20725    );
20726}
20727
20728#[gpui::test]
20729async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20730    let (buffer_id, mut cx) = setup_indent_guides_editor(
20731        &"
20732        block1
20733            block2
20734                block3
20735                    block4
20736            block2
20737        block1
20738        block1"
20739            .unindent(),
20740        cx,
20741    )
20742    .await;
20743
20744    assert_indent_guides(
20745        1..10,
20746        vec![
20747            indent_guide(buffer_id, 1, 4, 0),
20748            indent_guide(buffer_id, 2, 3, 1),
20749            indent_guide(buffer_id, 3, 3, 2),
20750        ],
20751        None,
20752        &mut cx,
20753    );
20754}
20755
20756#[gpui::test]
20757async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20758    let (buffer_id, mut cx) = setup_indent_guides_editor(
20759        &"
20760        block1
20761            block2
20762                block3
20763
20764        block1
20765        block1"
20766            .unindent(),
20767        cx,
20768    )
20769    .await;
20770
20771    assert_indent_guides(
20772        0..6,
20773        vec![
20774            indent_guide(buffer_id, 1, 2, 0),
20775            indent_guide(buffer_id, 2, 2, 1),
20776        ],
20777        None,
20778        &mut cx,
20779    );
20780}
20781
20782#[gpui::test]
20783async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20784    let (buffer_id, mut cx) = setup_indent_guides_editor(
20785        &"
20786        function component() {
20787        \treturn (
20788        \t\t\t
20789        \t\t<div>
20790        \t\t\t<abc></abc>
20791        \t\t</div>
20792        \t)
20793        }"
20794        .unindent(),
20795        cx,
20796    )
20797    .await;
20798
20799    assert_indent_guides(
20800        0..8,
20801        vec![
20802            indent_guide(buffer_id, 1, 6, 0),
20803            indent_guide(buffer_id, 2, 5, 1),
20804            indent_guide(buffer_id, 4, 4, 2),
20805        ],
20806        None,
20807        &mut cx,
20808    );
20809}
20810
20811#[gpui::test]
20812async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20813    let (buffer_id, mut cx) = setup_indent_guides_editor(
20814        &"
20815        function component() {
20816        \treturn (
20817        \t
20818        \t\t<div>
20819        \t\t\t<abc></abc>
20820        \t\t</div>
20821        \t)
20822        }"
20823        .unindent(),
20824        cx,
20825    )
20826    .await;
20827
20828    assert_indent_guides(
20829        0..8,
20830        vec![
20831            indent_guide(buffer_id, 1, 6, 0),
20832            indent_guide(buffer_id, 2, 5, 1),
20833            indent_guide(buffer_id, 4, 4, 2),
20834        ],
20835        None,
20836        &mut cx,
20837    );
20838}
20839
20840#[gpui::test]
20841async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20842    let (buffer_id, mut cx) = setup_indent_guides_editor(
20843        &"
20844        block1
20845
20846
20847
20848            block2
20849        "
20850        .unindent(),
20851        cx,
20852    )
20853    .await;
20854
20855    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20856}
20857
20858#[gpui::test]
20859async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20860    let (buffer_id, mut cx) = setup_indent_guides_editor(
20861        &"
20862        def a:
20863        \tb = 3
20864        \tif True:
20865        \t\tc = 4
20866        \t\td = 5
20867        \tprint(b)
20868        "
20869        .unindent(),
20870        cx,
20871    )
20872    .await;
20873
20874    assert_indent_guides(
20875        0..6,
20876        vec![
20877            indent_guide(buffer_id, 1, 5, 0),
20878            indent_guide(buffer_id, 3, 4, 1),
20879        ],
20880        None,
20881        &mut cx,
20882    );
20883}
20884
20885#[gpui::test]
20886async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20887    let (buffer_id, mut cx) = setup_indent_guides_editor(
20888        &"
20889    fn main() {
20890        let a = 1;
20891    }"
20892        .unindent(),
20893        cx,
20894    )
20895    .await;
20896
20897    cx.update_editor(|editor, window, cx| {
20898        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20899            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20900        });
20901    });
20902
20903    assert_indent_guides(
20904        0..3,
20905        vec![indent_guide(buffer_id, 1, 1, 0)],
20906        Some(vec![0]),
20907        &mut cx,
20908    );
20909}
20910
20911#[gpui::test]
20912async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20913    let (buffer_id, mut cx) = setup_indent_guides_editor(
20914        &"
20915    fn main() {
20916        if 1 == 2 {
20917            let a = 1;
20918        }
20919    }"
20920        .unindent(),
20921        cx,
20922    )
20923    .await;
20924
20925    cx.update_editor(|editor, window, cx| {
20926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20927            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20928        });
20929    });
20930
20931    assert_indent_guides(
20932        0..4,
20933        vec![
20934            indent_guide(buffer_id, 1, 3, 0),
20935            indent_guide(buffer_id, 2, 2, 1),
20936        ],
20937        Some(vec![1]),
20938        &mut cx,
20939    );
20940
20941    cx.update_editor(|editor, window, cx| {
20942        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20943            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20944        });
20945    });
20946
20947    assert_indent_guides(
20948        0..4,
20949        vec![
20950            indent_guide(buffer_id, 1, 3, 0),
20951            indent_guide(buffer_id, 2, 2, 1),
20952        ],
20953        Some(vec![1]),
20954        &mut cx,
20955    );
20956
20957    cx.update_editor(|editor, window, cx| {
20958        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20959            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20960        });
20961    });
20962
20963    assert_indent_guides(
20964        0..4,
20965        vec![
20966            indent_guide(buffer_id, 1, 3, 0),
20967            indent_guide(buffer_id, 2, 2, 1),
20968        ],
20969        Some(vec![0]),
20970        &mut cx,
20971    );
20972}
20973
20974#[gpui::test]
20975async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20976    let (buffer_id, mut cx) = setup_indent_guides_editor(
20977        &"
20978    fn main() {
20979        let a = 1;
20980
20981        let b = 2;
20982    }"
20983        .unindent(),
20984        cx,
20985    )
20986    .await;
20987
20988    cx.update_editor(|editor, window, cx| {
20989        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20990            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20991        });
20992    });
20993
20994    assert_indent_guides(
20995        0..5,
20996        vec![indent_guide(buffer_id, 1, 3, 0)],
20997        Some(vec![0]),
20998        &mut cx,
20999    );
21000}
21001
21002#[gpui::test]
21003async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21004    let (buffer_id, mut cx) = setup_indent_guides_editor(
21005        &"
21006    def m:
21007        a = 1
21008        pass"
21009            .unindent(),
21010        cx,
21011    )
21012    .await;
21013
21014    cx.update_editor(|editor, window, cx| {
21015        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21016            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21017        });
21018    });
21019
21020    assert_indent_guides(
21021        0..3,
21022        vec![indent_guide(buffer_id, 1, 2, 0)],
21023        Some(vec![0]),
21024        &mut cx,
21025    );
21026}
21027
21028#[gpui::test]
21029async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21030    init_test(cx, |_| {});
21031    let mut cx = EditorTestContext::new(cx).await;
21032    let text = indoc! {
21033        "
21034        impl A {
21035            fn b() {
21036                0;
21037                3;
21038                5;
21039                6;
21040                7;
21041            }
21042        }
21043        "
21044    };
21045    let base_text = indoc! {
21046        "
21047        impl A {
21048            fn b() {
21049                0;
21050                1;
21051                2;
21052                3;
21053                4;
21054            }
21055            fn c() {
21056                5;
21057                6;
21058                7;
21059            }
21060        }
21061        "
21062    };
21063
21064    cx.update_editor(|editor, window, cx| {
21065        editor.set_text(text, window, cx);
21066
21067        editor.buffer().update(cx, |multibuffer, cx| {
21068            let buffer = multibuffer.as_singleton().unwrap();
21069            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21070
21071            multibuffer.set_all_diff_hunks_expanded(cx);
21072            multibuffer.add_diff(diff, cx);
21073
21074            buffer.read(cx).remote_id()
21075        })
21076    });
21077    cx.run_until_parked();
21078
21079    cx.assert_state_with_diff(
21080        indoc! { "
21081          impl A {
21082              fn b() {
21083                  0;
21084        -         1;
21085        -         2;
21086                  3;
21087        -         4;
21088        -     }
21089        -     fn c() {
21090                  5;
21091                  6;
21092                  7;
21093              }
21094          }
21095          ˇ"
21096        }
21097        .to_string(),
21098    );
21099
21100    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21101        editor
21102            .snapshot(window, cx)
21103            .buffer_snapshot()
21104            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21105            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21106            .collect::<Vec<_>>()
21107    });
21108    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21109    assert_eq!(
21110        actual_guides,
21111        vec![
21112            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21113            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21114            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21115        ]
21116    );
21117}
21118
21119#[gpui::test]
21120async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21121    init_test(cx, |_| {});
21122    let mut cx = EditorTestContext::new(cx).await;
21123
21124    let diff_base = r#"
21125        a
21126        b
21127        c
21128        "#
21129    .unindent();
21130
21131    cx.set_state(
21132        &r#"
21133        ˇA
21134        b
21135        C
21136        "#
21137        .unindent(),
21138    );
21139    cx.set_head_text(&diff_base);
21140    cx.update_editor(|editor, window, cx| {
21141        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21142    });
21143    executor.run_until_parked();
21144
21145    let both_hunks_expanded = r#"
21146        - a
21147        + ˇA
21148          b
21149        - c
21150        + C
21151        "#
21152    .unindent();
21153
21154    cx.assert_state_with_diff(both_hunks_expanded.clone());
21155
21156    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21157        let snapshot = editor.snapshot(window, cx);
21158        let hunks = editor
21159            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21160            .collect::<Vec<_>>();
21161        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21162        let buffer_id = hunks[0].buffer_id;
21163        hunks
21164            .into_iter()
21165            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21166            .collect::<Vec<_>>()
21167    });
21168    assert_eq!(hunk_ranges.len(), 2);
21169
21170    cx.update_editor(|editor, _, cx| {
21171        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21172    });
21173    executor.run_until_parked();
21174
21175    let second_hunk_expanded = r#"
21176          ˇA
21177          b
21178        - c
21179        + C
21180        "#
21181    .unindent();
21182
21183    cx.assert_state_with_diff(second_hunk_expanded);
21184
21185    cx.update_editor(|editor, _, cx| {
21186        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21187    });
21188    executor.run_until_parked();
21189
21190    cx.assert_state_with_diff(both_hunks_expanded.clone());
21191
21192    cx.update_editor(|editor, _, cx| {
21193        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21194    });
21195    executor.run_until_parked();
21196
21197    let first_hunk_expanded = r#"
21198        - a
21199        + ˇA
21200          b
21201          C
21202        "#
21203    .unindent();
21204
21205    cx.assert_state_with_diff(first_hunk_expanded);
21206
21207    cx.update_editor(|editor, _, cx| {
21208        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21209    });
21210    executor.run_until_parked();
21211
21212    cx.assert_state_with_diff(both_hunks_expanded);
21213
21214    cx.set_state(
21215        &r#"
21216        ˇA
21217        b
21218        "#
21219        .unindent(),
21220    );
21221    cx.run_until_parked();
21222
21223    // TODO this cursor position seems bad
21224    cx.assert_state_with_diff(
21225        r#"
21226        - ˇa
21227        + A
21228          b
21229        "#
21230        .unindent(),
21231    );
21232
21233    cx.update_editor(|editor, window, cx| {
21234        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21235    });
21236
21237    cx.assert_state_with_diff(
21238        r#"
21239            - ˇa
21240            + A
21241              b
21242            - c
21243            "#
21244        .unindent(),
21245    );
21246
21247    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21248        let snapshot = editor.snapshot(window, cx);
21249        let hunks = editor
21250            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21251            .collect::<Vec<_>>();
21252        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21253        let buffer_id = hunks[0].buffer_id;
21254        hunks
21255            .into_iter()
21256            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21257            .collect::<Vec<_>>()
21258    });
21259    assert_eq!(hunk_ranges.len(), 2);
21260
21261    cx.update_editor(|editor, _, cx| {
21262        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21263    });
21264    executor.run_until_parked();
21265
21266    cx.assert_state_with_diff(
21267        r#"
21268        - ˇa
21269        + A
21270          b
21271        "#
21272        .unindent(),
21273    );
21274}
21275
21276#[gpui::test]
21277async fn test_toggle_deletion_hunk_at_start_of_file(
21278    executor: BackgroundExecutor,
21279    cx: &mut TestAppContext,
21280) {
21281    init_test(cx, |_| {});
21282    let mut cx = EditorTestContext::new(cx).await;
21283
21284    let diff_base = r#"
21285        a
21286        b
21287        c
21288        "#
21289    .unindent();
21290
21291    cx.set_state(
21292        &r#"
21293        ˇb
21294        c
21295        "#
21296        .unindent(),
21297    );
21298    cx.set_head_text(&diff_base);
21299    cx.update_editor(|editor, window, cx| {
21300        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21301    });
21302    executor.run_until_parked();
21303
21304    let hunk_expanded = r#"
21305        - a
21306          ˇb
21307          c
21308        "#
21309    .unindent();
21310
21311    cx.assert_state_with_diff(hunk_expanded.clone());
21312
21313    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21314        let snapshot = editor.snapshot(window, cx);
21315        let hunks = editor
21316            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21317            .collect::<Vec<_>>();
21318        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21319        let buffer_id = hunks[0].buffer_id;
21320        hunks
21321            .into_iter()
21322            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21323            .collect::<Vec<_>>()
21324    });
21325    assert_eq!(hunk_ranges.len(), 1);
21326
21327    cx.update_editor(|editor, _, cx| {
21328        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21329    });
21330    executor.run_until_parked();
21331
21332    let hunk_collapsed = r#"
21333          ˇb
21334          c
21335        "#
21336    .unindent();
21337
21338    cx.assert_state_with_diff(hunk_collapsed);
21339
21340    cx.update_editor(|editor, _, cx| {
21341        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21342    });
21343    executor.run_until_parked();
21344
21345    cx.assert_state_with_diff(hunk_expanded);
21346}
21347
21348#[gpui::test]
21349async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21350    init_test(cx, |_| {});
21351
21352    let fs = FakeFs::new(cx.executor());
21353    fs.insert_tree(
21354        path!("/test"),
21355        json!({
21356            ".git": {},
21357            "file-1": "ONE\n",
21358            "file-2": "TWO\n",
21359            "file-3": "THREE\n",
21360        }),
21361    )
21362    .await;
21363
21364    fs.set_head_for_repo(
21365        path!("/test/.git").as_ref(),
21366        &[
21367            ("file-1", "one\n".into()),
21368            ("file-2", "two\n".into()),
21369            ("file-3", "three\n".into()),
21370        ],
21371        "deadbeef",
21372    );
21373
21374    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21375    let mut buffers = vec![];
21376    for i in 1..=3 {
21377        let buffer = project
21378            .update(cx, |project, cx| {
21379                let path = format!(path!("/test/file-{}"), i);
21380                project.open_local_buffer(path, cx)
21381            })
21382            .await
21383            .unwrap();
21384        buffers.push(buffer);
21385    }
21386
21387    let multibuffer = cx.new(|cx| {
21388        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21389        multibuffer.set_all_diff_hunks_expanded(cx);
21390        for buffer in &buffers {
21391            let snapshot = buffer.read(cx).snapshot();
21392            multibuffer.set_excerpts_for_path(
21393                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21394                buffer.clone(),
21395                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21396                2,
21397                cx,
21398            );
21399        }
21400        multibuffer
21401    });
21402
21403    let editor = cx.add_window(|window, cx| {
21404        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21405    });
21406    cx.run_until_parked();
21407
21408    let snapshot = editor
21409        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21410        .unwrap();
21411    let hunks = snapshot
21412        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21413        .map(|hunk| match hunk {
21414            DisplayDiffHunk::Unfolded {
21415                display_row_range, ..
21416            } => display_row_range,
21417            DisplayDiffHunk::Folded { .. } => unreachable!(),
21418        })
21419        .collect::<Vec<_>>();
21420    assert_eq!(
21421        hunks,
21422        [
21423            DisplayRow(2)..DisplayRow(4),
21424            DisplayRow(7)..DisplayRow(9),
21425            DisplayRow(12)..DisplayRow(14),
21426        ]
21427    );
21428}
21429
21430#[gpui::test]
21431async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21432    init_test(cx, |_| {});
21433
21434    let mut cx = EditorTestContext::new(cx).await;
21435    cx.set_head_text(indoc! { "
21436        one
21437        two
21438        three
21439        four
21440        five
21441        "
21442    });
21443    cx.set_index_text(indoc! { "
21444        one
21445        two
21446        three
21447        four
21448        five
21449        "
21450    });
21451    cx.set_state(indoc! {"
21452        one
21453        TWO
21454        ˇTHREE
21455        FOUR
21456        five
21457    "});
21458    cx.run_until_parked();
21459    cx.update_editor(|editor, window, cx| {
21460        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21461    });
21462    cx.run_until_parked();
21463    cx.assert_index_text(Some(indoc! {"
21464        one
21465        TWO
21466        THREE
21467        FOUR
21468        five
21469    "}));
21470    cx.set_state(indoc! { "
21471        one
21472        TWO
21473        ˇTHREE-HUNDRED
21474        FOUR
21475        five
21476    "});
21477    cx.run_until_parked();
21478    cx.update_editor(|editor, window, cx| {
21479        let snapshot = editor.snapshot(window, cx);
21480        let hunks = editor
21481            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21482            .collect::<Vec<_>>();
21483        assert_eq!(hunks.len(), 1);
21484        assert_eq!(
21485            hunks[0].status(),
21486            DiffHunkStatus {
21487                kind: DiffHunkStatusKind::Modified,
21488                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21489            }
21490        );
21491
21492        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21493    });
21494    cx.run_until_parked();
21495    cx.assert_index_text(Some(indoc! {"
21496        one
21497        TWO
21498        THREE-HUNDRED
21499        FOUR
21500        five
21501    "}));
21502}
21503
21504#[gpui::test]
21505fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21506    init_test(cx, |_| {});
21507
21508    let editor = cx.add_window(|window, cx| {
21509        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21510        build_editor(buffer, window, cx)
21511    });
21512
21513    let render_args = Arc::new(Mutex::new(None));
21514    let snapshot = editor
21515        .update(cx, |editor, window, cx| {
21516            let snapshot = editor.buffer().read(cx).snapshot(cx);
21517            let range =
21518                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21519
21520            struct RenderArgs {
21521                row: MultiBufferRow,
21522                folded: bool,
21523                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21524            }
21525
21526            let crease = Crease::inline(
21527                range,
21528                FoldPlaceholder::test(),
21529                {
21530                    let toggle_callback = render_args.clone();
21531                    move |row, folded, callback, _window, _cx| {
21532                        *toggle_callback.lock() = Some(RenderArgs {
21533                            row,
21534                            folded,
21535                            callback,
21536                        });
21537                        div()
21538                    }
21539                },
21540                |_row, _folded, _window, _cx| div(),
21541            );
21542
21543            editor.insert_creases(Some(crease), cx);
21544            let snapshot = editor.snapshot(window, cx);
21545            let _div =
21546                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21547            snapshot
21548        })
21549        .unwrap();
21550
21551    let render_args = render_args.lock().take().unwrap();
21552    assert_eq!(render_args.row, MultiBufferRow(1));
21553    assert!(!render_args.folded);
21554    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21555
21556    cx.update_window(*editor, |_, window, cx| {
21557        (render_args.callback)(true, window, cx)
21558    })
21559    .unwrap();
21560    let snapshot = editor
21561        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21562        .unwrap();
21563    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21564
21565    cx.update_window(*editor, |_, window, cx| {
21566        (render_args.callback)(false, window, cx)
21567    })
21568    .unwrap();
21569    let snapshot = editor
21570        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21571        .unwrap();
21572    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21573}
21574
21575#[gpui::test]
21576async fn test_input_text(cx: &mut TestAppContext) {
21577    init_test(cx, |_| {});
21578    let mut cx = EditorTestContext::new(cx).await;
21579
21580    cx.set_state(
21581        &r#"ˇone
21582        two
21583
21584        three
21585        fourˇ
21586        five
21587
21588        siˇx"#
21589            .unindent(),
21590    );
21591
21592    cx.dispatch_action(HandleInput(String::new()));
21593    cx.assert_editor_state(
21594        &r#"ˇone
21595        two
21596
21597        three
21598        fourˇ
21599        five
21600
21601        siˇx"#
21602            .unindent(),
21603    );
21604
21605    cx.dispatch_action(HandleInput("AAAA".to_string()));
21606    cx.assert_editor_state(
21607        &r#"AAAAˇone
21608        two
21609
21610        three
21611        fourAAAAˇ
21612        five
21613
21614        siAAAAˇx"#
21615            .unindent(),
21616    );
21617}
21618
21619#[gpui::test]
21620async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21621    init_test(cx, |_| {});
21622
21623    let mut cx = EditorTestContext::new(cx).await;
21624    cx.set_state(
21625        r#"let foo = 1;
21626let foo = 2;
21627let foo = 3;
21628let fooˇ = 4;
21629let foo = 5;
21630let foo = 6;
21631let foo = 7;
21632let foo = 8;
21633let foo = 9;
21634let foo = 10;
21635let foo = 11;
21636let foo = 12;
21637let foo = 13;
21638let foo = 14;
21639let foo = 15;"#,
21640    );
21641
21642    cx.update_editor(|e, window, cx| {
21643        assert_eq!(
21644            e.next_scroll_position,
21645            NextScrollCursorCenterTopBottom::Center,
21646            "Default next scroll direction is center",
21647        );
21648
21649        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21650        assert_eq!(
21651            e.next_scroll_position,
21652            NextScrollCursorCenterTopBottom::Top,
21653            "After center, next scroll direction should be top",
21654        );
21655
21656        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21657        assert_eq!(
21658            e.next_scroll_position,
21659            NextScrollCursorCenterTopBottom::Bottom,
21660            "After top, next scroll direction should be bottom",
21661        );
21662
21663        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21664        assert_eq!(
21665            e.next_scroll_position,
21666            NextScrollCursorCenterTopBottom::Center,
21667            "After bottom, scrolling should start over",
21668        );
21669
21670        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21671        assert_eq!(
21672            e.next_scroll_position,
21673            NextScrollCursorCenterTopBottom::Top,
21674            "Scrolling continues if retriggered fast enough"
21675        );
21676    });
21677
21678    cx.executor()
21679        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21680    cx.executor().run_until_parked();
21681    cx.update_editor(|e, _, _| {
21682        assert_eq!(
21683            e.next_scroll_position,
21684            NextScrollCursorCenterTopBottom::Center,
21685            "If scrolling is not triggered fast enough, it should reset"
21686        );
21687    });
21688}
21689
21690#[gpui::test]
21691async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21692    init_test(cx, |_| {});
21693    let mut cx = EditorLspTestContext::new_rust(
21694        lsp::ServerCapabilities {
21695            definition_provider: Some(lsp::OneOf::Left(true)),
21696            references_provider: Some(lsp::OneOf::Left(true)),
21697            ..lsp::ServerCapabilities::default()
21698        },
21699        cx,
21700    )
21701    .await;
21702
21703    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21704        let go_to_definition = cx
21705            .lsp
21706            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21707                move |params, _| async move {
21708                    if empty_go_to_definition {
21709                        Ok(None)
21710                    } else {
21711                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21712                            uri: params.text_document_position_params.text_document.uri,
21713                            range: lsp::Range::new(
21714                                lsp::Position::new(4, 3),
21715                                lsp::Position::new(4, 6),
21716                            ),
21717                        })))
21718                    }
21719                },
21720            );
21721        let references = cx
21722            .lsp
21723            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21724                Ok(Some(vec![lsp::Location {
21725                    uri: params.text_document_position.text_document.uri,
21726                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21727                }]))
21728            });
21729        (go_to_definition, references)
21730    };
21731
21732    cx.set_state(
21733        &r#"fn one() {
21734            let mut a = ˇtwo();
21735        }
21736
21737        fn two() {}"#
21738            .unindent(),
21739    );
21740    set_up_lsp_handlers(false, &mut cx);
21741    let navigated = cx
21742        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21743        .await
21744        .expect("Failed to navigate to definition");
21745    assert_eq!(
21746        navigated,
21747        Navigated::Yes,
21748        "Should have navigated to definition from the GetDefinition response"
21749    );
21750    cx.assert_editor_state(
21751        &r#"fn one() {
21752            let mut a = two();
21753        }
21754
21755        fn «twoˇ»() {}"#
21756            .unindent(),
21757    );
21758
21759    let editors = cx.update_workspace(|workspace, _, cx| {
21760        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21761    });
21762    cx.update_editor(|_, _, test_editor_cx| {
21763        assert_eq!(
21764            editors.len(),
21765            1,
21766            "Initially, only one, test, editor should be open in the workspace"
21767        );
21768        assert_eq!(
21769            test_editor_cx.entity(),
21770            editors.last().expect("Asserted len is 1").clone()
21771        );
21772    });
21773
21774    set_up_lsp_handlers(true, &mut cx);
21775    let navigated = cx
21776        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21777        .await
21778        .expect("Failed to navigate to lookup references");
21779    assert_eq!(
21780        navigated,
21781        Navigated::Yes,
21782        "Should have navigated to references as a fallback after empty GoToDefinition response"
21783    );
21784    // We should not change the selections in the existing file,
21785    // if opening another milti buffer with the references
21786    cx.assert_editor_state(
21787        &r#"fn one() {
21788            let mut a = two();
21789        }
21790
21791        fn «twoˇ»() {}"#
21792            .unindent(),
21793    );
21794    let editors = cx.update_workspace(|workspace, _, cx| {
21795        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21796    });
21797    cx.update_editor(|_, _, test_editor_cx| {
21798        assert_eq!(
21799            editors.len(),
21800            2,
21801            "After falling back to references search, we open a new editor with the results"
21802        );
21803        let references_fallback_text = editors
21804            .into_iter()
21805            .find(|new_editor| *new_editor != test_editor_cx.entity())
21806            .expect("Should have one non-test editor now")
21807            .read(test_editor_cx)
21808            .text(test_editor_cx);
21809        assert_eq!(
21810            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21811            "Should use the range from the references response and not the GoToDefinition one"
21812        );
21813    });
21814}
21815
21816#[gpui::test]
21817async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21818    init_test(cx, |_| {});
21819    cx.update(|cx| {
21820        let mut editor_settings = EditorSettings::get_global(cx).clone();
21821        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21822        EditorSettings::override_global(editor_settings, cx);
21823    });
21824    let mut cx = EditorLspTestContext::new_rust(
21825        lsp::ServerCapabilities {
21826            definition_provider: Some(lsp::OneOf::Left(true)),
21827            references_provider: Some(lsp::OneOf::Left(true)),
21828            ..lsp::ServerCapabilities::default()
21829        },
21830        cx,
21831    )
21832    .await;
21833    let original_state = r#"fn one() {
21834        let mut a = ˇtwo();
21835    }
21836
21837    fn two() {}"#
21838        .unindent();
21839    cx.set_state(&original_state);
21840
21841    let mut go_to_definition = cx
21842        .lsp
21843        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21844            move |_, _| async move { Ok(None) },
21845        );
21846    let _references = cx
21847        .lsp
21848        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21849            panic!("Should not call for references with no go to definition fallback")
21850        });
21851
21852    let navigated = cx
21853        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21854        .await
21855        .expect("Failed to navigate to lookup references");
21856    go_to_definition
21857        .next()
21858        .await
21859        .expect("Should have called the go_to_definition handler");
21860
21861    assert_eq!(
21862        navigated,
21863        Navigated::No,
21864        "Should have navigated to references as a fallback after empty GoToDefinition response"
21865    );
21866    cx.assert_editor_state(&original_state);
21867    let editors = cx.update_workspace(|workspace, _, cx| {
21868        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21869    });
21870    cx.update_editor(|_, _, _| {
21871        assert_eq!(
21872            editors.len(),
21873            1,
21874            "After unsuccessful fallback, no other editor should have been opened"
21875        );
21876    });
21877}
21878
21879#[gpui::test]
21880async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21881    init_test(cx, |_| {});
21882    let mut cx = EditorLspTestContext::new_rust(
21883        lsp::ServerCapabilities {
21884            references_provider: Some(lsp::OneOf::Left(true)),
21885            ..lsp::ServerCapabilities::default()
21886        },
21887        cx,
21888    )
21889    .await;
21890
21891    cx.set_state(
21892        &r#"
21893        fn one() {
21894            let mut a = two();
21895        }
21896
21897        fn ˇtwo() {}"#
21898            .unindent(),
21899    );
21900    cx.lsp
21901        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21902            Ok(Some(vec![
21903                lsp::Location {
21904                    uri: params.text_document_position.text_document.uri.clone(),
21905                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21906                },
21907                lsp::Location {
21908                    uri: params.text_document_position.text_document.uri,
21909                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21910                },
21911            ]))
21912        });
21913    let navigated = cx
21914        .update_editor(|editor, window, cx| {
21915            editor.find_all_references(&FindAllReferences, window, cx)
21916        })
21917        .unwrap()
21918        .await
21919        .expect("Failed to navigate to references");
21920    assert_eq!(
21921        navigated,
21922        Navigated::Yes,
21923        "Should have navigated to references from the FindAllReferences response"
21924    );
21925    cx.assert_editor_state(
21926        &r#"fn one() {
21927            let mut a = two();
21928        }
21929
21930        fn ˇtwo() {}"#
21931            .unindent(),
21932    );
21933
21934    let editors = cx.update_workspace(|workspace, _, cx| {
21935        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21936    });
21937    cx.update_editor(|_, _, _| {
21938        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21939    });
21940
21941    cx.set_state(
21942        &r#"fn one() {
21943            let mut a = ˇtwo();
21944        }
21945
21946        fn two() {}"#
21947            .unindent(),
21948    );
21949    let navigated = cx
21950        .update_editor(|editor, window, cx| {
21951            editor.find_all_references(&FindAllReferences, window, cx)
21952        })
21953        .unwrap()
21954        .await
21955        .expect("Failed to navigate to references");
21956    assert_eq!(
21957        navigated,
21958        Navigated::Yes,
21959        "Should have navigated to references from the FindAllReferences response"
21960    );
21961    cx.assert_editor_state(
21962        &r#"fn one() {
21963            let mut a = ˇtwo();
21964        }
21965
21966        fn two() {}"#
21967            .unindent(),
21968    );
21969    let editors = cx.update_workspace(|workspace, _, cx| {
21970        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21971    });
21972    cx.update_editor(|_, _, _| {
21973        assert_eq!(
21974            editors.len(),
21975            2,
21976            "should have re-used the previous multibuffer"
21977        );
21978    });
21979
21980    cx.set_state(
21981        &r#"fn one() {
21982            let mut a = ˇtwo();
21983        }
21984        fn three() {}
21985        fn two() {}"#
21986            .unindent(),
21987    );
21988    cx.lsp
21989        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21990            Ok(Some(vec![
21991                lsp::Location {
21992                    uri: params.text_document_position.text_document.uri.clone(),
21993                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21994                },
21995                lsp::Location {
21996                    uri: params.text_document_position.text_document.uri,
21997                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21998                },
21999            ]))
22000        });
22001    let navigated = cx
22002        .update_editor(|editor, window, cx| {
22003            editor.find_all_references(&FindAllReferences, window, cx)
22004        })
22005        .unwrap()
22006        .await
22007        .expect("Failed to navigate to references");
22008    assert_eq!(
22009        navigated,
22010        Navigated::Yes,
22011        "Should have navigated to references from the FindAllReferences response"
22012    );
22013    cx.assert_editor_state(
22014        &r#"fn one() {
22015                let mut a = ˇtwo();
22016            }
22017            fn three() {}
22018            fn two() {}"#
22019            .unindent(),
22020    );
22021    let editors = cx.update_workspace(|workspace, _, cx| {
22022        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22023    });
22024    cx.update_editor(|_, _, _| {
22025        assert_eq!(
22026            editors.len(),
22027            3,
22028            "should have used a new multibuffer as offsets changed"
22029        );
22030    });
22031}
22032#[gpui::test]
22033async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22034    init_test(cx, |_| {});
22035
22036    let language = Arc::new(Language::new(
22037        LanguageConfig::default(),
22038        Some(tree_sitter_rust::LANGUAGE.into()),
22039    ));
22040
22041    let text = r#"
22042        #[cfg(test)]
22043        mod tests() {
22044            #[test]
22045            fn runnable_1() {
22046                let a = 1;
22047            }
22048
22049            #[test]
22050            fn runnable_2() {
22051                let a = 1;
22052                let b = 2;
22053            }
22054        }
22055    "#
22056    .unindent();
22057
22058    let fs = FakeFs::new(cx.executor());
22059    fs.insert_file("/file.rs", Default::default()).await;
22060
22061    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22062    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22063    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22064    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22065    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22066
22067    let editor = cx.new_window_entity(|window, cx| {
22068        Editor::new(
22069            EditorMode::full(),
22070            multi_buffer,
22071            Some(project.clone()),
22072            window,
22073            cx,
22074        )
22075    });
22076
22077    editor.update_in(cx, |editor, window, cx| {
22078        let snapshot = editor.buffer().read(cx).snapshot(cx);
22079        editor.tasks.insert(
22080            (buffer.read(cx).remote_id(), 3),
22081            RunnableTasks {
22082                templates: vec![],
22083                offset: snapshot.anchor_before(43),
22084                column: 0,
22085                extra_variables: HashMap::default(),
22086                context_range: BufferOffset(43)..BufferOffset(85),
22087            },
22088        );
22089        editor.tasks.insert(
22090            (buffer.read(cx).remote_id(), 8),
22091            RunnableTasks {
22092                templates: vec![],
22093                offset: snapshot.anchor_before(86),
22094                column: 0,
22095                extra_variables: HashMap::default(),
22096                context_range: BufferOffset(86)..BufferOffset(191),
22097            },
22098        );
22099
22100        // Test finding task when cursor is inside function body
22101        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22102            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22103        });
22104        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22105        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22106
22107        // Test finding task when cursor is on function name
22108        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22109            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22110        });
22111        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22112        assert_eq!(row, 8, "Should find task when cursor is on function name");
22113    });
22114}
22115
22116#[gpui::test]
22117async fn test_folding_buffers(cx: &mut TestAppContext) {
22118    init_test(cx, |_| {});
22119
22120    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22121    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22122    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22123
22124    let fs = FakeFs::new(cx.executor());
22125    fs.insert_tree(
22126        path!("/a"),
22127        json!({
22128            "first.rs": sample_text_1,
22129            "second.rs": sample_text_2,
22130            "third.rs": sample_text_3,
22131        }),
22132    )
22133    .await;
22134    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22135    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22136    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22137    let worktree = project.update(cx, |project, cx| {
22138        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22139        assert_eq!(worktrees.len(), 1);
22140        worktrees.pop().unwrap()
22141    });
22142    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22143
22144    let buffer_1 = project
22145        .update(cx, |project, cx| {
22146            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22147        })
22148        .await
22149        .unwrap();
22150    let buffer_2 = project
22151        .update(cx, |project, cx| {
22152            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22153        })
22154        .await
22155        .unwrap();
22156    let buffer_3 = project
22157        .update(cx, |project, cx| {
22158            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22159        })
22160        .await
22161        .unwrap();
22162
22163    let multi_buffer = cx.new(|cx| {
22164        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22165        multi_buffer.push_excerpts(
22166            buffer_1.clone(),
22167            [
22168                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22169                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22170                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22171            ],
22172            cx,
22173        );
22174        multi_buffer.push_excerpts(
22175            buffer_2.clone(),
22176            [
22177                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22178                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22179                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22180            ],
22181            cx,
22182        );
22183        multi_buffer.push_excerpts(
22184            buffer_3.clone(),
22185            [
22186                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22187                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22188                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22189            ],
22190            cx,
22191        );
22192        multi_buffer
22193    });
22194    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22195        Editor::new(
22196            EditorMode::full(),
22197            multi_buffer.clone(),
22198            Some(project.clone()),
22199            window,
22200            cx,
22201        )
22202    });
22203
22204    assert_eq!(
22205        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22206        "\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",
22207    );
22208
22209    multi_buffer_editor.update(cx, |editor, cx| {
22210        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22211    });
22212    assert_eq!(
22213        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22214        "\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",
22215        "After folding the first buffer, its text should not be displayed"
22216    );
22217
22218    multi_buffer_editor.update(cx, |editor, cx| {
22219        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22220    });
22221    assert_eq!(
22222        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22223        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22224        "After folding the second buffer, its text should not be displayed"
22225    );
22226
22227    multi_buffer_editor.update(cx, |editor, cx| {
22228        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22229    });
22230    assert_eq!(
22231        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22232        "\n\n\n\n\n",
22233        "After folding the third buffer, its text should not be displayed"
22234    );
22235
22236    // Emulate selection inside the fold logic, that should work
22237    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22238        editor
22239            .snapshot(window, cx)
22240            .next_line_boundary(Point::new(0, 4));
22241    });
22242
22243    multi_buffer_editor.update(cx, |editor, cx| {
22244        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22245    });
22246    assert_eq!(
22247        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22248        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22249        "After unfolding the second buffer, its text should be displayed"
22250    );
22251
22252    // Typing inside of buffer 1 causes that buffer to be unfolded.
22253    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22254        assert_eq!(
22255            multi_buffer
22256                .read(cx)
22257                .snapshot(cx)
22258                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22259                .collect::<String>(),
22260            "bbbb"
22261        );
22262        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22263            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22264        });
22265        editor.handle_input("B", window, cx);
22266    });
22267
22268    assert_eq!(
22269        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22270        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22271        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22272    );
22273
22274    multi_buffer_editor.update(cx, |editor, cx| {
22275        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22276    });
22277    assert_eq!(
22278        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22279        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22280        "After unfolding the all buffers, all original text should be displayed"
22281    );
22282}
22283
22284#[gpui::test]
22285async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22286    init_test(cx, |_| {});
22287
22288    let sample_text_1 = "1111\n2222\n3333".to_string();
22289    let sample_text_2 = "4444\n5555\n6666".to_string();
22290    let sample_text_3 = "7777\n8888\n9999".to_string();
22291
22292    let fs = FakeFs::new(cx.executor());
22293    fs.insert_tree(
22294        path!("/a"),
22295        json!({
22296            "first.rs": sample_text_1,
22297            "second.rs": sample_text_2,
22298            "third.rs": sample_text_3,
22299        }),
22300    )
22301    .await;
22302    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22303    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22304    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22305    let worktree = project.update(cx, |project, cx| {
22306        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22307        assert_eq!(worktrees.len(), 1);
22308        worktrees.pop().unwrap()
22309    });
22310    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22311
22312    let buffer_1 = project
22313        .update(cx, |project, cx| {
22314            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22315        })
22316        .await
22317        .unwrap();
22318    let buffer_2 = project
22319        .update(cx, |project, cx| {
22320            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22321        })
22322        .await
22323        .unwrap();
22324    let buffer_3 = project
22325        .update(cx, |project, cx| {
22326            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22327        })
22328        .await
22329        .unwrap();
22330
22331    let multi_buffer = cx.new(|cx| {
22332        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22333        multi_buffer.push_excerpts(
22334            buffer_1.clone(),
22335            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22336            cx,
22337        );
22338        multi_buffer.push_excerpts(
22339            buffer_2.clone(),
22340            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22341            cx,
22342        );
22343        multi_buffer.push_excerpts(
22344            buffer_3.clone(),
22345            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22346            cx,
22347        );
22348        multi_buffer
22349    });
22350
22351    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22352        Editor::new(
22353            EditorMode::full(),
22354            multi_buffer,
22355            Some(project.clone()),
22356            window,
22357            cx,
22358        )
22359    });
22360
22361    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22362    assert_eq!(
22363        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22364        full_text,
22365    );
22366
22367    multi_buffer_editor.update(cx, |editor, cx| {
22368        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22369    });
22370    assert_eq!(
22371        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22372        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22373        "After folding the first buffer, its text should not be displayed"
22374    );
22375
22376    multi_buffer_editor.update(cx, |editor, cx| {
22377        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22378    });
22379
22380    assert_eq!(
22381        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22382        "\n\n\n\n\n\n7777\n8888\n9999",
22383        "After folding the second buffer, its text should not be displayed"
22384    );
22385
22386    multi_buffer_editor.update(cx, |editor, cx| {
22387        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22388    });
22389    assert_eq!(
22390        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22391        "\n\n\n\n\n",
22392        "After folding the third buffer, its text should not be displayed"
22393    );
22394
22395    multi_buffer_editor.update(cx, |editor, cx| {
22396        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22397    });
22398    assert_eq!(
22399        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22400        "\n\n\n\n4444\n5555\n6666\n\n",
22401        "After unfolding the second buffer, its text should be displayed"
22402    );
22403
22404    multi_buffer_editor.update(cx, |editor, cx| {
22405        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22406    });
22407    assert_eq!(
22408        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22409        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22410        "After unfolding the first buffer, its text should be displayed"
22411    );
22412
22413    multi_buffer_editor.update(cx, |editor, cx| {
22414        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22415    });
22416    assert_eq!(
22417        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22418        full_text,
22419        "After unfolding all buffers, all original text should be displayed"
22420    );
22421}
22422
22423#[gpui::test]
22424async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22425    init_test(cx, |_| {});
22426
22427    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22428
22429    let fs = FakeFs::new(cx.executor());
22430    fs.insert_tree(
22431        path!("/a"),
22432        json!({
22433            "main.rs": sample_text,
22434        }),
22435    )
22436    .await;
22437    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22438    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22439    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22440    let worktree = project.update(cx, |project, cx| {
22441        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22442        assert_eq!(worktrees.len(), 1);
22443        worktrees.pop().unwrap()
22444    });
22445    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22446
22447    let buffer_1 = project
22448        .update(cx, |project, cx| {
22449            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22450        })
22451        .await
22452        .unwrap();
22453
22454    let multi_buffer = cx.new(|cx| {
22455        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22456        multi_buffer.push_excerpts(
22457            buffer_1.clone(),
22458            [ExcerptRange::new(
22459                Point::new(0, 0)
22460                    ..Point::new(
22461                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22462                        0,
22463                    ),
22464            )],
22465            cx,
22466        );
22467        multi_buffer
22468    });
22469    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22470        Editor::new(
22471            EditorMode::full(),
22472            multi_buffer,
22473            Some(project.clone()),
22474            window,
22475            cx,
22476        )
22477    });
22478
22479    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22480    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22481        enum TestHighlight {}
22482        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22483        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22484        editor.highlight_text::<TestHighlight>(
22485            vec![highlight_range.clone()],
22486            HighlightStyle::color(Hsla::green()),
22487            cx,
22488        );
22489        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22490            s.select_ranges(Some(highlight_range))
22491        });
22492    });
22493
22494    let full_text = format!("\n\n{sample_text}");
22495    assert_eq!(
22496        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22497        full_text,
22498    );
22499}
22500
22501#[gpui::test]
22502async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22503    init_test(cx, |_| {});
22504    cx.update(|cx| {
22505        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22506            "keymaps/default-linux.json",
22507            cx,
22508        )
22509        .unwrap();
22510        cx.bind_keys(default_key_bindings);
22511    });
22512
22513    let (editor, cx) = cx.add_window_view(|window, cx| {
22514        let multi_buffer = MultiBuffer::build_multi(
22515            [
22516                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22517                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22518                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22519                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22520            ],
22521            cx,
22522        );
22523        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22524
22525        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22526        // fold all but the second buffer, so that we test navigating between two
22527        // adjacent folded buffers, as well as folded buffers at the start and
22528        // end the multibuffer
22529        editor.fold_buffer(buffer_ids[0], cx);
22530        editor.fold_buffer(buffer_ids[2], cx);
22531        editor.fold_buffer(buffer_ids[3], cx);
22532
22533        editor
22534    });
22535    cx.simulate_resize(size(px(1000.), px(1000.)));
22536
22537    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22538    cx.assert_excerpts_with_selections(indoc! {"
22539        [EXCERPT]
22540        ˇ[FOLDED]
22541        [EXCERPT]
22542        a1
22543        b1
22544        [EXCERPT]
22545        [FOLDED]
22546        [EXCERPT]
22547        [FOLDED]
22548        "
22549    });
22550    cx.simulate_keystroke("down");
22551    cx.assert_excerpts_with_selections(indoc! {"
22552        [EXCERPT]
22553        [FOLDED]
22554        [EXCERPT]
22555        ˇa1
22556        b1
22557        [EXCERPT]
22558        [FOLDED]
22559        [EXCERPT]
22560        [FOLDED]
22561        "
22562    });
22563    cx.simulate_keystroke("down");
22564    cx.assert_excerpts_with_selections(indoc! {"
22565        [EXCERPT]
22566        [FOLDED]
22567        [EXCERPT]
22568        a1
22569        ˇb1
22570        [EXCERPT]
22571        [FOLDED]
22572        [EXCERPT]
22573        [FOLDED]
22574        "
22575    });
22576    cx.simulate_keystroke("down");
22577    cx.assert_excerpts_with_selections(indoc! {"
22578        [EXCERPT]
22579        [FOLDED]
22580        [EXCERPT]
22581        a1
22582        b1
22583        ˇ[EXCERPT]
22584        [FOLDED]
22585        [EXCERPT]
22586        [FOLDED]
22587        "
22588    });
22589    cx.simulate_keystroke("down");
22590    cx.assert_excerpts_with_selections(indoc! {"
22591        [EXCERPT]
22592        [FOLDED]
22593        [EXCERPT]
22594        a1
22595        b1
22596        [EXCERPT]
22597        ˇ[FOLDED]
22598        [EXCERPT]
22599        [FOLDED]
22600        "
22601    });
22602    for _ in 0..5 {
22603        cx.simulate_keystroke("down");
22604        cx.assert_excerpts_with_selections(indoc! {"
22605            [EXCERPT]
22606            [FOLDED]
22607            [EXCERPT]
22608            a1
22609            b1
22610            [EXCERPT]
22611            [FOLDED]
22612            [EXCERPT]
22613            ˇ[FOLDED]
22614            "
22615        });
22616    }
22617
22618    cx.simulate_keystroke("up");
22619    cx.assert_excerpts_with_selections(indoc! {"
22620        [EXCERPT]
22621        [FOLDED]
22622        [EXCERPT]
22623        a1
22624        b1
22625        [EXCERPT]
22626        ˇ[FOLDED]
22627        [EXCERPT]
22628        [FOLDED]
22629        "
22630    });
22631    cx.simulate_keystroke("up");
22632    cx.assert_excerpts_with_selections(indoc! {"
22633        [EXCERPT]
22634        [FOLDED]
22635        [EXCERPT]
22636        a1
22637        b1
22638        ˇ[EXCERPT]
22639        [FOLDED]
22640        [EXCERPT]
22641        [FOLDED]
22642        "
22643    });
22644    cx.simulate_keystroke("up");
22645    cx.assert_excerpts_with_selections(indoc! {"
22646        [EXCERPT]
22647        [FOLDED]
22648        [EXCERPT]
22649        a1
22650        ˇb1
22651        [EXCERPT]
22652        [FOLDED]
22653        [EXCERPT]
22654        [FOLDED]
22655        "
22656    });
22657    cx.simulate_keystroke("up");
22658    cx.assert_excerpts_with_selections(indoc! {"
22659        [EXCERPT]
22660        [FOLDED]
22661        [EXCERPT]
22662        ˇa1
22663        b1
22664        [EXCERPT]
22665        [FOLDED]
22666        [EXCERPT]
22667        [FOLDED]
22668        "
22669    });
22670    for _ in 0..5 {
22671        cx.simulate_keystroke("up");
22672        cx.assert_excerpts_with_selections(indoc! {"
22673            [EXCERPT]
22674            ˇ[FOLDED]
22675            [EXCERPT]
22676            a1
22677            b1
22678            [EXCERPT]
22679            [FOLDED]
22680            [EXCERPT]
22681            [FOLDED]
22682            "
22683        });
22684    }
22685}
22686
22687#[gpui::test]
22688async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22689    init_test(cx, |_| {});
22690
22691    // Simple insertion
22692    assert_highlighted_edits(
22693        "Hello, world!",
22694        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22695        true,
22696        cx,
22697        |highlighted_edits, cx| {
22698            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22699            assert_eq!(highlighted_edits.highlights.len(), 1);
22700            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22701            assert_eq!(
22702                highlighted_edits.highlights[0].1.background_color,
22703                Some(cx.theme().status().created_background)
22704            );
22705        },
22706    )
22707    .await;
22708
22709    // Replacement
22710    assert_highlighted_edits(
22711        "This is a test.",
22712        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22713        false,
22714        cx,
22715        |highlighted_edits, cx| {
22716            assert_eq!(highlighted_edits.text, "That is a test.");
22717            assert_eq!(highlighted_edits.highlights.len(), 1);
22718            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22719            assert_eq!(
22720                highlighted_edits.highlights[0].1.background_color,
22721                Some(cx.theme().status().created_background)
22722            );
22723        },
22724    )
22725    .await;
22726
22727    // Multiple edits
22728    assert_highlighted_edits(
22729        "Hello, world!",
22730        vec![
22731            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22732            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22733        ],
22734        false,
22735        cx,
22736        |highlighted_edits, cx| {
22737            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22738            assert_eq!(highlighted_edits.highlights.len(), 2);
22739            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22740            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22741            assert_eq!(
22742                highlighted_edits.highlights[0].1.background_color,
22743                Some(cx.theme().status().created_background)
22744            );
22745            assert_eq!(
22746                highlighted_edits.highlights[1].1.background_color,
22747                Some(cx.theme().status().created_background)
22748            );
22749        },
22750    )
22751    .await;
22752
22753    // Multiple lines with edits
22754    assert_highlighted_edits(
22755        "First line\nSecond line\nThird line\nFourth line",
22756        vec![
22757            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22758            (
22759                Point::new(2, 0)..Point::new(2, 10),
22760                "New third line".to_string(),
22761            ),
22762            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22763        ],
22764        false,
22765        cx,
22766        |highlighted_edits, cx| {
22767            assert_eq!(
22768                highlighted_edits.text,
22769                "Second modified\nNew third line\nFourth updated line"
22770            );
22771            assert_eq!(highlighted_edits.highlights.len(), 3);
22772            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22773            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22774            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22775            for highlight in &highlighted_edits.highlights {
22776                assert_eq!(
22777                    highlight.1.background_color,
22778                    Some(cx.theme().status().created_background)
22779                );
22780            }
22781        },
22782    )
22783    .await;
22784}
22785
22786#[gpui::test]
22787async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22788    init_test(cx, |_| {});
22789
22790    // Deletion
22791    assert_highlighted_edits(
22792        "Hello, world!",
22793        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22794        true,
22795        cx,
22796        |highlighted_edits, cx| {
22797            assert_eq!(highlighted_edits.text, "Hello, world!");
22798            assert_eq!(highlighted_edits.highlights.len(), 1);
22799            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22800            assert_eq!(
22801                highlighted_edits.highlights[0].1.background_color,
22802                Some(cx.theme().status().deleted_background)
22803            );
22804        },
22805    )
22806    .await;
22807
22808    // Insertion
22809    assert_highlighted_edits(
22810        "Hello, world!",
22811        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22812        true,
22813        cx,
22814        |highlighted_edits, cx| {
22815            assert_eq!(highlighted_edits.highlights.len(), 1);
22816            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22817            assert_eq!(
22818                highlighted_edits.highlights[0].1.background_color,
22819                Some(cx.theme().status().created_background)
22820            );
22821        },
22822    )
22823    .await;
22824}
22825
22826async fn assert_highlighted_edits(
22827    text: &str,
22828    edits: Vec<(Range<Point>, String)>,
22829    include_deletions: bool,
22830    cx: &mut TestAppContext,
22831    assertion_fn: impl Fn(HighlightedText, &App),
22832) {
22833    let window = cx.add_window(|window, cx| {
22834        let buffer = MultiBuffer::build_simple(text, cx);
22835        Editor::new(EditorMode::full(), buffer, None, window, cx)
22836    });
22837    let cx = &mut VisualTestContext::from_window(*window, cx);
22838
22839    let (buffer, snapshot) = window
22840        .update(cx, |editor, _window, cx| {
22841            (
22842                editor.buffer().clone(),
22843                editor.buffer().read(cx).snapshot(cx),
22844            )
22845        })
22846        .unwrap();
22847
22848    let edits = edits
22849        .into_iter()
22850        .map(|(range, edit)| {
22851            (
22852                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22853                edit,
22854            )
22855        })
22856        .collect::<Vec<_>>();
22857
22858    let text_anchor_edits = edits
22859        .clone()
22860        .into_iter()
22861        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
22862        .collect::<Vec<_>>();
22863
22864    let edit_preview = window
22865        .update(cx, |_, _window, cx| {
22866            buffer
22867                .read(cx)
22868                .as_singleton()
22869                .unwrap()
22870                .read(cx)
22871                .preview_edits(text_anchor_edits.into(), cx)
22872        })
22873        .unwrap()
22874        .await;
22875
22876    cx.update(|_window, cx| {
22877        let highlighted_edits = edit_prediction_edit_text(
22878            snapshot.as_singleton().unwrap().2,
22879            &edits,
22880            &edit_preview,
22881            include_deletions,
22882            cx,
22883        );
22884        assertion_fn(highlighted_edits, cx)
22885    });
22886}
22887
22888#[track_caller]
22889fn assert_breakpoint(
22890    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22891    path: &Arc<Path>,
22892    expected: Vec<(u32, Breakpoint)>,
22893) {
22894    if expected.is_empty() {
22895        assert!(!breakpoints.contains_key(path), "{}", path.display());
22896    } else {
22897        let mut breakpoint = breakpoints
22898            .get(path)
22899            .unwrap()
22900            .iter()
22901            .map(|breakpoint| {
22902                (
22903                    breakpoint.row,
22904                    Breakpoint {
22905                        message: breakpoint.message.clone(),
22906                        state: breakpoint.state,
22907                        condition: breakpoint.condition.clone(),
22908                        hit_condition: breakpoint.hit_condition.clone(),
22909                    },
22910                )
22911            })
22912            .collect::<Vec<_>>();
22913
22914        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22915
22916        assert_eq!(expected, breakpoint);
22917    }
22918}
22919
22920fn add_log_breakpoint_at_cursor(
22921    editor: &mut Editor,
22922    log_message: &str,
22923    window: &mut Window,
22924    cx: &mut Context<Editor>,
22925) {
22926    let (anchor, bp) = editor
22927        .breakpoints_at_cursors(window, cx)
22928        .first()
22929        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22930        .unwrap_or_else(|| {
22931            let snapshot = editor.snapshot(window, cx);
22932            let cursor_position: Point =
22933                editor.selections.newest(&snapshot.display_snapshot).head();
22934
22935            let breakpoint_position = snapshot
22936                .buffer_snapshot()
22937                .anchor_before(Point::new(cursor_position.row, 0));
22938
22939            (breakpoint_position, Breakpoint::new_log(log_message))
22940        });
22941
22942    editor.edit_breakpoint_at_anchor(
22943        anchor,
22944        bp,
22945        BreakpointEditAction::EditLogMessage(log_message.into()),
22946        cx,
22947    );
22948}
22949
22950#[gpui::test]
22951async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22952    init_test(cx, |_| {});
22953
22954    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22955    let fs = FakeFs::new(cx.executor());
22956    fs.insert_tree(
22957        path!("/a"),
22958        json!({
22959            "main.rs": sample_text,
22960        }),
22961    )
22962    .await;
22963    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22964    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22965    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22966
22967    let fs = FakeFs::new(cx.executor());
22968    fs.insert_tree(
22969        path!("/a"),
22970        json!({
22971            "main.rs": sample_text,
22972        }),
22973    )
22974    .await;
22975    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22976    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22977    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22978    let worktree_id = workspace
22979        .update(cx, |workspace, _window, cx| {
22980            workspace.project().update(cx, |project, cx| {
22981                project.worktrees(cx).next().unwrap().read(cx).id()
22982            })
22983        })
22984        .unwrap();
22985
22986    let buffer = project
22987        .update(cx, |project, cx| {
22988            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22989        })
22990        .await
22991        .unwrap();
22992
22993    let (editor, cx) = cx.add_window_view(|window, cx| {
22994        Editor::new(
22995            EditorMode::full(),
22996            MultiBuffer::build_from_buffer(buffer, cx),
22997            Some(project.clone()),
22998            window,
22999            cx,
23000        )
23001    });
23002
23003    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23004    let abs_path = project.read_with(cx, |project, cx| {
23005        project
23006            .absolute_path(&project_path, cx)
23007            .map(Arc::from)
23008            .unwrap()
23009    });
23010
23011    // assert we can add breakpoint on the first line
23012    editor.update_in(cx, |editor, window, cx| {
23013        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23014        editor.move_to_end(&MoveToEnd, window, cx);
23015        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23016    });
23017
23018    let breakpoints = editor.update(cx, |editor, cx| {
23019        editor
23020            .breakpoint_store()
23021            .as_ref()
23022            .unwrap()
23023            .read(cx)
23024            .all_source_breakpoints(cx)
23025    });
23026
23027    assert_eq!(1, breakpoints.len());
23028    assert_breakpoint(
23029        &breakpoints,
23030        &abs_path,
23031        vec![
23032            (0, Breakpoint::new_standard()),
23033            (3, Breakpoint::new_standard()),
23034        ],
23035    );
23036
23037    editor.update_in(cx, |editor, window, cx| {
23038        editor.move_to_beginning(&MoveToBeginning, window, cx);
23039        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23040    });
23041
23042    let breakpoints = editor.update(cx, |editor, cx| {
23043        editor
23044            .breakpoint_store()
23045            .as_ref()
23046            .unwrap()
23047            .read(cx)
23048            .all_source_breakpoints(cx)
23049    });
23050
23051    assert_eq!(1, breakpoints.len());
23052    assert_breakpoint(
23053        &breakpoints,
23054        &abs_path,
23055        vec![(3, Breakpoint::new_standard())],
23056    );
23057
23058    editor.update_in(cx, |editor, window, cx| {
23059        editor.move_to_end(&MoveToEnd, window, cx);
23060        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23061    });
23062
23063    let breakpoints = editor.update(cx, |editor, cx| {
23064        editor
23065            .breakpoint_store()
23066            .as_ref()
23067            .unwrap()
23068            .read(cx)
23069            .all_source_breakpoints(cx)
23070    });
23071
23072    assert_eq!(0, breakpoints.len());
23073    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23074}
23075
23076#[gpui::test]
23077async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23078    init_test(cx, |_| {});
23079
23080    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23081
23082    let fs = FakeFs::new(cx.executor());
23083    fs.insert_tree(
23084        path!("/a"),
23085        json!({
23086            "main.rs": sample_text,
23087        }),
23088    )
23089    .await;
23090    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23091    let (workspace, cx) =
23092        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23093
23094    let worktree_id = workspace.update(cx, |workspace, cx| {
23095        workspace.project().update(cx, |project, cx| {
23096            project.worktrees(cx).next().unwrap().read(cx).id()
23097        })
23098    });
23099
23100    let buffer = project
23101        .update(cx, |project, cx| {
23102            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23103        })
23104        .await
23105        .unwrap();
23106
23107    let (editor, cx) = cx.add_window_view(|window, cx| {
23108        Editor::new(
23109            EditorMode::full(),
23110            MultiBuffer::build_from_buffer(buffer, cx),
23111            Some(project.clone()),
23112            window,
23113            cx,
23114        )
23115    });
23116
23117    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23118    let abs_path = project.read_with(cx, |project, cx| {
23119        project
23120            .absolute_path(&project_path, cx)
23121            .map(Arc::from)
23122            .unwrap()
23123    });
23124
23125    editor.update_in(cx, |editor, window, cx| {
23126        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23127    });
23128
23129    let breakpoints = editor.update(cx, |editor, cx| {
23130        editor
23131            .breakpoint_store()
23132            .as_ref()
23133            .unwrap()
23134            .read(cx)
23135            .all_source_breakpoints(cx)
23136    });
23137
23138    assert_breakpoint(
23139        &breakpoints,
23140        &abs_path,
23141        vec![(0, Breakpoint::new_log("hello world"))],
23142    );
23143
23144    // Removing a log message from a log breakpoint should remove it
23145    editor.update_in(cx, |editor, window, cx| {
23146        add_log_breakpoint_at_cursor(editor, "", window, cx);
23147    });
23148
23149    let breakpoints = editor.update(cx, |editor, cx| {
23150        editor
23151            .breakpoint_store()
23152            .as_ref()
23153            .unwrap()
23154            .read(cx)
23155            .all_source_breakpoints(cx)
23156    });
23157
23158    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23159
23160    editor.update_in(cx, |editor, window, cx| {
23161        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23162        editor.move_to_end(&MoveToEnd, window, cx);
23163        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23164        // Not adding a log message to a standard breakpoint shouldn't remove it
23165        add_log_breakpoint_at_cursor(editor, "", window, cx);
23166    });
23167
23168    let breakpoints = editor.update(cx, |editor, cx| {
23169        editor
23170            .breakpoint_store()
23171            .as_ref()
23172            .unwrap()
23173            .read(cx)
23174            .all_source_breakpoints(cx)
23175    });
23176
23177    assert_breakpoint(
23178        &breakpoints,
23179        &abs_path,
23180        vec![
23181            (0, Breakpoint::new_standard()),
23182            (3, Breakpoint::new_standard()),
23183        ],
23184    );
23185
23186    editor.update_in(cx, |editor, window, cx| {
23187        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23188    });
23189
23190    let breakpoints = editor.update(cx, |editor, cx| {
23191        editor
23192            .breakpoint_store()
23193            .as_ref()
23194            .unwrap()
23195            .read(cx)
23196            .all_source_breakpoints(cx)
23197    });
23198
23199    assert_breakpoint(
23200        &breakpoints,
23201        &abs_path,
23202        vec![
23203            (0, Breakpoint::new_standard()),
23204            (3, Breakpoint::new_log("hello world")),
23205        ],
23206    );
23207
23208    editor.update_in(cx, |editor, window, cx| {
23209        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23210    });
23211
23212    let breakpoints = editor.update(cx, |editor, cx| {
23213        editor
23214            .breakpoint_store()
23215            .as_ref()
23216            .unwrap()
23217            .read(cx)
23218            .all_source_breakpoints(cx)
23219    });
23220
23221    assert_breakpoint(
23222        &breakpoints,
23223        &abs_path,
23224        vec![
23225            (0, Breakpoint::new_standard()),
23226            (3, Breakpoint::new_log("hello Earth!!")),
23227        ],
23228    );
23229}
23230
23231/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23232/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23233/// or when breakpoints were placed out of order. This tests for a regression too
23234#[gpui::test]
23235async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23236    init_test(cx, |_| {});
23237
23238    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23239    let fs = FakeFs::new(cx.executor());
23240    fs.insert_tree(
23241        path!("/a"),
23242        json!({
23243            "main.rs": sample_text,
23244        }),
23245    )
23246    .await;
23247    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23248    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23249    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23250
23251    let fs = FakeFs::new(cx.executor());
23252    fs.insert_tree(
23253        path!("/a"),
23254        json!({
23255            "main.rs": sample_text,
23256        }),
23257    )
23258    .await;
23259    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23260    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23261    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23262    let worktree_id = workspace
23263        .update(cx, |workspace, _window, cx| {
23264            workspace.project().update(cx, |project, cx| {
23265                project.worktrees(cx).next().unwrap().read(cx).id()
23266            })
23267        })
23268        .unwrap();
23269
23270    let buffer = project
23271        .update(cx, |project, cx| {
23272            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23273        })
23274        .await
23275        .unwrap();
23276
23277    let (editor, cx) = cx.add_window_view(|window, cx| {
23278        Editor::new(
23279            EditorMode::full(),
23280            MultiBuffer::build_from_buffer(buffer, cx),
23281            Some(project.clone()),
23282            window,
23283            cx,
23284        )
23285    });
23286
23287    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23288    let abs_path = project.read_with(cx, |project, cx| {
23289        project
23290            .absolute_path(&project_path, cx)
23291            .map(Arc::from)
23292            .unwrap()
23293    });
23294
23295    // assert we can add breakpoint on the first line
23296    editor.update_in(cx, |editor, window, cx| {
23297        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23298        editor.move_to_end(&MoveToEnd, window, cx);
23299        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23300        editor.move_up(&MoveUp, window, cx);
23301        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23302    });
23303
23304    let breakpoints = editor.update(cx, |editor, cx| {
23305        editor
23306            .breakpoint_store()
23307            .as_ref()
23308            .unwrap()
23309            .read(cx)
23310            .all_source_breakpoints(cx)
23311    });
23312
23313    assert_eq!(1, breakpoints.len());
23314    assert_breakpoint(
23315        &breakpoints,
23316        &abs_path,
23317        vec![
23318            (0, Breakpoint::new_standard()),
23319            (2, Breakpoint::new_standard()),
23320            (3, Breakpoint::new_standard()),
23321        ],
23322    );
23323
23324    editor.update_in(cx, |editor, window, cx| {
23325        editor.move_to_beginning(&MoveToBeginning, window, cx);
23326        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23327        editor.move_to_end(&MoveToEnd, window, cx);
23328        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23329        // Disabling a breakpoint that doesn't exist should do nothing
23330        editor.move_up(&MoveUp, window, cx);
23331        editor.move_up(&MoveUp, window, cx);
23332        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23333    });
23334
23335    let breakpoints = editor.update(cx, |editor, cx| {
23336        editor
23337            .breakpoint_store()
23338            .as_ref()
23339            .unwrap()
23340            .read(cx)
23341            .all_source_breakpoints(cx)
23342    });
23343
23344    let disable_breakpoint = {
23345        let mut bp = Breakpoint::new_standard();
23346        bp.state = BreakpointState::Disabled;
23347        bp
23348    };
23349
23350    assert_eq!(1, breakpoints.len());
23351    assert_breakpoint(
23352        &breakpoints,
23353        &abs_path,
23354        vec![
23355            (0, disable_breakpoint.clone()),
23356            (2, Breakpoint::new_standard()),
23357            (3, disable_breakpoint.clone()),
23358        ],
23359    );
23360
23361    editor.update_in(cx, |editor, window, cx| {
23362        editor.move_to_beginning(&MoveToBeginning, window, cx);
23363        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23364        editor.move_to_end(&MoveToEnd, window, cx);
23365        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23366        editor.move_up(&MoveUp, window, cx);
23367        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23368    });
23369
23370    let breakpoints = editor.update(cx, |editor, cx| {
23371        editor
23372            .breakpoint_store()
23373            .as_ref()
23374            .unwrap()
23375            .read(cx)
23376            .all_source_breakpoints(cx)
23377    });
23378
23379    assert_eq!(1, breakpoints.len());
23380    assert_breakpoint(
23381        &breakpoints,
23382        &abs_path,
23383        vec![
23384            (0, Breakpoint::new_standard()),
23385            (2, disable_breakpoint),
23386            (3, Breakpoint::new_standard()),
23387        ],
23388    );
23389}
23390
23391#[gpui::test]
23392async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23393    init_test(cx, |_| {});
23394    let capabilities = lsp::ServerCapabilities {
23395        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23396            prepare_provider: Some(true),
23397            work_done_progress_options: Default::default(),
23398        })),
23399        ..Default::default()
23400    };
23401    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23402
23403    cx.set_state(indoc! {"
23404        struct Fˇoo {}
23405    "});
23406
23407    cx.update_editor(|editor, _, cx| {
23408        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23409        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23410        editor.highlight_background::<DocumentHighlightRead>(
23411            &[highlight_range],
23412            |theme| theme.colors().editor_document_highlight_read_background,
23413            cx,
23414        );
23415    });
23416
23417    let mut prepare_rename_handler = cx
23418        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23419            move |_, _, _| async move {
23420                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23421                    start: lsp::Position {
23422                        line: 0,
23423                        character: 7,
23424                    },
23425                    end: lsp::Position {
23426                        line: 0,
23427                        character: 10,
23428                    },
23429                })))
23430            },
23431        );
23432    let prepare_rename_task = cx
23433        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23434        .expect("Prepare rename was not started");
23435    prepare_rename_handler.next().await.unwrap();
23436    prepare_rename_task.await.expect("Prepare rename failed");
23437
23438    let mut rename_handler =
23439        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23440            let edit = lsp::TextEdit {
23441                range: lsp::Range {
23442                    start: lsp::Position {
23443                        line: 0,
23444                        character: 7,
23445                    },
23446                    end: lsp::Position {
23447                        line: 0,
23448                        character: 10,
23449                    },
23450                },
23451                new_text: "FooRenamed".to_string(),
23452            };
23453            Ok(Some(lsp::WorkspaceEdit::new(
23454                // Specify the same edit twice
23455                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23456            )))
23457        });
23458    let rename_task = cx
23459        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23460        .expect("Confirm rename was not started");
23461    rename_handler.next().await.unwrap();
23462    rename_task.await.expect("Confirm rename failed");
23463    cx.run_until_parked();
23464
23465    // Despite two edits, only one is actually applied as those are identical
23466    cx.assert_editor_state(indoc! {"
23467        struct FooRenamedˇ {}
23468    "});
23469}
23470
23471#[gpui::test]
23472async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23473    init_test(cx, |_| {});
23474    // These capabilities indicate that the server does not support prepare rename.
23475    let capabilities = lsp::ServerCapabilities {
23476        rename_provider: Some(lsp::OneOf::Left(true)),
23477        ..Default::default()
23478    };
23479    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23480
23481    cx.set_state(indoc! {"
23482        struct Fˇoo {}
23483    "});
23484
23485    cx.update_editor(|editor, _window, cx| {
23486        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23487        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23488        editor.highlight_background::<DocumentHighlightRead>(
23489            &[highlight_range],
23490            |theme| theme.colors().editor_document_highlight_read_background,
23491            cx,
23492        );
23493    });
23494
23495    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23496        .expect("Prepare rename was not started")
23497        .await
23498        .expect("Prepare rename failed");
23499
23500    let mut rename_handler =
23501        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23502            let edit = lsp::TextEdit {
23503                range: lsp::Range {
23504                    start: lsp::Position {
23505                        line: 0,
23506                        character: 7,
23507                    },
23508                    end: lsp::Position {
23509                        line: 0,
23510                        character: 10,
23511                    },
23512                },
23513                new_text: "FooRenamed".to_string(),
23514            };
23515            Ok(Some(lsp::WorkspaceEdit::new(
23516                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23517            )))
23518        });
23519    let rename_task = cx
23520        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23521        .expect("Confirm rename was not started");
23522    rename_handler.next().await.unwrap();
23523    rename_task.await.expect("Confirm rename failed");
23524    cx.run_until_parked();
23525
23526    // Correct range is renamed, as `surrounding_word` is used to find it.
23527    cx.assert_editor_state(indoc! {"
23528        struct FooRenamedˇ {}
23529    "});
23530}
23531
23532#[gpui::test]
23533async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23534    init_test(cx, |_| {});
23535    let mut cx = EditorTestContext::new(cx).await;
23536
23537    let language = Arc::new(
23538        Language::new(
23539            LanguageConfig::default(),
23540            Some(tree_sitter_html::LANGUAGE.into()),
23541        )
23542        .with_brackets_query(
23543            r#"
23544            ("<" @open "/>" @close)
23545            ("</" @open ">" @close)
23546            ("<" @open ">" @close)
23547            ("\"" @open "\"" @close)
23548            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23549        "#,
23550        )
23551        .unwrap(),
23552    );
23553    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23554
23555    cx.set_state(indoc! {"
23556        <span>ˇ</span>
23557    "});
23558    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23559    cx.assert_editor_state(indoc! {"
23560        <span>
23561        ˇ
23562        </span>
23563    "});
23564
23565    cx.set_state(indoc! {"
23566        <span><span></span>ˇ</span>
23567    "});
23568    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23569    cx.assert_editor_state(indoc! {"
23570        <span><span></span>
23571        ˇ</span>
23572    "});
23573
23574    cx.set_state(indoc! {"
23575        <span>ˇ
23576        </span>
23577    "});
23578    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23579    cx.assert_editor_state(indoc! {"
23580        <span>
23581        ˇ
23582        </span>
23583    "});
23584}
23585
23586#[gpui::test(iterations = 10)]
23587async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23588    init_test(cx, |_| {});
23589
23590    let fs = FakeFs::new(cx.executor());
23591    fs.insert_tree(
23592        path!("/dir"),
23593        json!({
23594            "a.ts": "a",
23595        }),
23596    )
23597    .await;
23598
23599    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23600    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23601    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23602
23603    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23604    language_registry.add(Arc::new(Language::new(
23605        LanguageConfig {
23606            name: "TypeScript".into(),
23607            matcher: LanguageMatcher {
23608                path_suffixes: vec!["ts".to_string()],
23609                ..Default::default()
23610            },
23611            ..Default::default()
23612        },
23613        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23614    )));
23615    let mut fake_language_servers = language_registry.register_fake_lsp(
23616        "TypeScript",
23617        FakeLspAdapter {
23618            capabilities: lsp::ServerCapabilities {
23619                code_lens_provider: Some(lsp::CodeLensOptions {
23620                    resolve_provider: Some(true),
23621                }),
23622                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23623                    commands: vec!["_the/command".to_string()],
23624                    ..lsp::ExecuteCommandOptions::default()
23625                }),
23626                ..lsp::ServerCapabilities::default()
23627            },
23628            ..FakeLspAdapter::default()
23629        },
23630    );
23631
23632    let editor = workspace
23633        .update(cx, |workspace, window, cx| {
23634            workspace.open_abs_path(
23635                PathBuf::from(path!("/dir/a.ts")),
23636                OpenOptions::default(),
23637                window,
23638                cx,
23639            )
23640        })
23641        .unwrap()
23642        .await
23643        .unwrap()
23644        .downcast::<Editor>()
23645        .unwrap();
23646    cx.executor().run_until_parked();
23647
23648    let fake_server = fake_language_servers.next().await.unwrap();
23649
23650    let buffer = editor.update(cx, |editor, cx| {
23651        editor
23652            .buffer()
23653            .read(cx)
23654            .as_singleton()
23655            .expect("have opened a single file by path")
23656    });
23657
23658    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23659    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23660    drop(buffer_snapshot);
23661    let actions = cx
23662        .update_window(*workspace, |_, window, cx| {
23663            project.code_actions(&buffer, anchor..anchor, window, cx)
23664        })
23665        .unwrap();
23666
23667    fake_server
23668        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23669            Ok(Some(vec![
23670                lsp::CodeLens {
23671                    range: lsp::Range::default(),
23672                    command: Some(lsp::Command {
23673                        title: "Code lens command".to_owned(),
23674                        command: "_the/command".to_owned(),
23675                        arguments: None,
23676                    }),
23677                    data: None,
23678                },
23679                lsp::CodeLens {
23680                    range: lsp::Range::default(),
23681                    command: Some(lsp::Command {
23682                        title: "Command not in capabilities".to_owned(),
23683                        command: "not in capabilities".to_owned(),
23684                        arguments: None,
23685                    }),
23686                    data: None,
23687                },
23688                lsp::CodeLens {
23689                    range: lsp::Range {
23690                        start: lsp::Position {
23691                            line: 1,
23692                            character: 1,
23693                        },
23694                        end: lsp::Position {
23695                            line: 1,
23696                            character: 1,
23697                        },
23698                    },
23699                    command: Some(lsp::Command {
23700                        title: "Command not in range".to_owned(),
23701                        command: "_the/command".to_owned(),
23702                        arguments: None,
23703                    }),
23704                    data: None,
23705                },
23706            ]))
23707        })
23708        .next()
23709        .await;
23710
23711    let actions = actions.await.unwrap();
23712    assert_eq!(
23713        actions.len(),
23714        1,
23715        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23716    );
23717    let action = actions[0].clone();
23718    let apply = project.update(cx, |project, cx| {
23719        project.apply_code_action(buffer.clone(), action, true, cx)
23720    });
23721
23722    // Resolving the code action does not populate its edits. In absence of
23723    // edits, we must execute the given command.
23724    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23725        |mut lens, _| async move {
23726            let lens_command = lens.command.as_mut().expect("should have a command");
23727            assert_eq!(lens_command.title, "Code lens command");
23728            lens_command.arguments = Some(vec![json!("the-argument")]);
23729            Ok(lens)
23730        },
23731    );
23732
23733    // While executing the command, the language server sends the editor
23734    // a `workspaceEdit` request.
23735    fake_server
23736        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23737            let fake = fake_server.clone();
23738            move |params, _| {
23739                assert_eq!(params.command, "_the/command");
23740                let fake = fake.clone();
23741                async move {
23742                    fake.server
23743                        .request::<lsp::request::ApplyWorkspaceEdit>(
23744                            lsp::ApplyWorkspaceEditParams {
23745                                label: None,
23746                                edit: lsp::WorkspaceEdit {
23747                                    changes: Some(
23748                                        [(
23749                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23750                                            vec![lsp::TextEdit {
23751                                                range: lsp::Range::new(
23752                                                    lsp::Position::new(0, 0),
23753                                                    lsp::Position::new(0, 0),
23754                                                ),
23755                                                new_text: "X".into(),
23756                                            }],
23757                                        )]
23758                                        .into_iter()
23759                                        .collect(),
23760                                    ),
23761                                    ..lsp::WorkspaceEdit::default()
23762                                },
23763                            },
23764                        )
23765                        .await
23766                        .into_response()
23767                        .unwrap();
23768                    Ok(Some(json!(null)))
23769                }
23770            }
23771        })
23772        .next()
23773        .await;
23774
23775    // Applying the code lens command returns a project transaction containing the edits
23776    // sent by the language server in its `workspaceEdit` request.
23777    let transaction = apply.await.unwrap();
23778    assert!(transaction.0.contains_key(&buffer));
23779    buffer.update(cx, |buffer, cx| {
23780        assert_eq!(buffer.text(), "Xa");
23781        buffer.undo(cx);
23782        assert_eq!(buffer.text(), "a");
23783    });
23784
23785    let actions_after_edits = cx
23786        .update_window(*workspace, |_, window, cx| {
23787            project.code_actions(&buffer, anchor..anchor, window, cx)
23788        })
23789        .unwrap()
23790        .await
23791        .unwrap();
23792    assert_eq!(
23793        actions, actions_after_edits,
23794        "For the same selection, same code lens actions should be returned"
23795    );
23796
23797    let _responses =
23798        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23799            panic!("No more code lens requests are expected");
23800        });
23801    editor.update_in(cx, |editor, window, cx| {
23802        editor.select_all(&SelectAll, window, cx);
23803    });
23804    cx.executor().run_until_parked();
23805    let new_actions = cx
23806        .update_window(*workspace, |_, window, cx| {
23807            project.code_actions(&buffer, anchor..anchor, window, cx)
23808        })
23809        .unwrap()
23810        .await
23811        .unwrap();
23812    assert_eq!(
23813        actions, new_actions,
23814        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23815    );
23816}
23817
23818#[gpui::test]
23819async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23820    init_test(cx, |_| {});
23821
23822    let fs = FakeFs::new(cx.executor());
23823    let main_text = r#"fn main() {
23824println!("1");
23825println!("2");
23826println!("3");
23827println!("4");
23828println!("5");
23829}"#;
23830    let lib_text = "mod foo {}";
23831    fs.insert_tree(
23832        path!("/a"),
23833        json!({
23834            "lib.rs": lib_text,
23835            "main.rs": main_text,
23836        }),
23837    )
23838    .await;
23839
23840    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23841    let (workspace, cx) =
23842        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23843    let worktree_id = workspace.update(cx, |workspace, cx| {
23844        workspace.project().update(cx, |project, cx| {
23845            project.worktrees(cx).next().unwrap().read(cx).id()
23846        })
23847    });
23848
23849    let expected_ranges = vec![
23850        Point::new(0, 0)..Point::new(0, 0),
23851        Point::new(1, 0)..Point::new(1, 1),
23852        Point::new(2, 0)..Point::new(2, 2),
23853        Point::new(3, 0)..Point::new(3, 3),
23854    ];
23855
23856    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23857    let editor_1 = workspace
23858        .update_in(cx, |workspace, window, cx| {
23859            workspace.open_path(
23860                (worktree_id, rel_path("main.rs")),
23861                Some(pane_1.downgrade()),
23862                true,
23863                window,
23864                cx,
23865            )
23866        })
23867        .unwrap()
23868        .await
23869        .downcast::<Editor>()
23870        .unwrap();
23871    pane_1.update(cx, |pane, cx| {
23872        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23873        open_editor.update(cx, |editor, cx| {
23874            assert_eq!(
23875                editor.display_text(cx),
23876                main_text,
23877                "Original main.rs text on initial open",
23878            );
23879            assert_eq!(
23880                editor
23881                    .selections
23882                    .all::<Point>(&editor.display_snapshot(cx))
23883                    .into_iter()
23884                    .map(|s| s.range())
23885                    .collect::<Vec<_>>(),
23886                vec![Point::zero()..Point::zero()],
23887                "Default selections on initial open",
23888            );
23889        })
23890    });
23891    editor_1.update_in(cx, |editor, window, cx| {
23892        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23893            s.select_ranges(expected_ranges.clone());
23894        });
23895    });
23896
23897    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23898        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23899    });
23900    let editor_2 = workspace
23901        .update_in(cx, |workspace, window, cx| {
23902            workspace.open_path(
23903                (worktree_id, rel_path("main.rs")),
23904                Some(pane_2.downgrade()),
23905                true,
23906                window,
23907                cx,
23908            )
23909        })
23910        .unwrap()
23911        .await
23912        .downcast::<Editor>()
23913        .unwrap();
23914    pane_2.update(cx, |pane, cx| {
23915        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23916        open_editor.update(cx, |editor, cx| {
23917            assert_eq!(
23918                editor.display_text(cx),
23919                main_text,
23920                "Original main.rs text on initial open in another panel",
23921            );
23922            assert_eq!(
23923                editor
23924                    .selections
23925                    .all::<Point>(&editor.display_snapshot(cx))
23926                    .into_iter()
23927                    .map(|s| s.range())
23928                    .collect::<Vec<_>>(),
23929                vec![Point::zero()..Point::zero()],
23930                "Default selections on initial open in another panel",
23931            );
23932        })
23933    });
23934
23935    editor_2.update_in(cx, |editor, window, cx| {
23936        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23937    });
23938
23939    let _other_editor_1 = workspace
23940        .update_in(cx, |workspace, window, cx| {
23941            workspace.open_path(
23942                (worktree_id, rel_path("lib.rs")),
23943                Some(pane_1.downgrade()),
23944                true,
23945                window,
23946                cx,
23947            )
23948        })
23949        .unwrap()
23950        .await
23951        .downcast::<Editor>()
23952        .unwrap();
23953    pane_1
23954        .update_in(cx, |pane, window, cx| {
23955            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23956        })
23957        .await
23958        .unwrap();
23959    drop(editor_1);
23960    pane_1.update(cx, |pane, cx| {
23961        pane.active_item()
23962            .unwrap()
23963            .downcast::<Editor>()
23964            .unwrap()
23965            .update(cx, |editor, cx| {
23966                assert_eq!(
23967                    editor.display_text(cx),
23968                    lib_text,
23969                    "Other file should be open and active",
23970                );
23971            });
23972        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23973    });
23974
23975    let _other_editor_2 = workspace
23976        .update_in(cx, |workspace, window, cx| {
23977            workspace.open_path(
23978                (worktree_id, rel_path("lib.rs")),
23979                Some(pane_2.downgrade()),
23980                true,
23981                window,
23982                cx,
23983            )
23984        })
23985        .unwrap()
23986        .await
23987        .downcast::<Editor>()
23988        .unwrap();
23989    pane_2
23990        .update_in(cx, |pane, window, cx| {
23991            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23992        })
23993        .await
23994        .unwrap();
23995    drop(editor_2);
23996    pane_2.update(cx, |pane, cx| {
23997        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23998        open_editor.update(cx, |editor, cx| {
23999            assert_eq!(
24000                editor.display_text(cx),
24001                lib_text,
24002                "Other file should be open and active in another panel too",
24003            );
24004        });
24005        assert_eq!(
24006            pane.items().count(),
24007            1,
24008            "No other editors should be open in another pane",
24009        );
24010    });
24011
24012    let _editor_1_reopened = workspace
24013        .update_in(cx, |workspace, window, cx| {
24014            workspace.open_path(
24015                (worktree_id, rel_path("main.rs")),
24016                Some(pane_1.downgrade()),
24017                true,
24018                window,
24019                cx,
24020            )
24021        })
24022        .unwrap()
24023        .await
24024        .downcast::<Editor>()
24025        .unwrap();
24026    let _editor_2_reopened = workspace
24027        .update_in(cx, |workspace, window, cx| {
24028            workspace.open_path(
24029                (worktree_id, rel_path("main.rs")),
24030                Some(pane_2.downgrade()),
24031                true,
24032                window,
24033                cx,
24034            )
24035        })
24036        .unwrap()
24037        .await
24038        .downcast::<Editor>()
24039        .unwrap();
24040    pane_1.update(cx, |pane, cx| {
24041        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24042        open_editor.update(cx, |editor, cx| {
24043            assert_eq!(
24044                editor.display_text(cx),
24045                main_text,
24046                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24047            );
24048            assert_eq!(
24049                editor
24050                    .selections
24051                    .all::<Point>(&editor.display_snapshot(cx))
24052                    .into_iter()
24053                    .map(|s| s.range())
24054                    .collect::<Vec<_>>(),
24055                expected_ranges,
24056                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24057            );
24058        })
24059    });
24060    pane_2.update(cx, |pane, cx| {
24061        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24062        open_editor.update(cx, |editor, cx| {
24063            assert_eq!(
24064                editor.display_text(cx),
24065                r#"fn main() {
24066⋯rintln!("1");
24067⋯intln!("2");
24068⋯ntln!("3");
24069println!("4");
24070println!("5");
24071}"#,
24072                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24073            );
24074            assert_eq!(
24075                editor
24076                    .selections
24077                    .all::<Point>(&editor.display_snapshot(cx))
24078                    .into_iter()
24079                    .map(|s| s.range())
24080                    .collect::<Vec<_>>(),
24081                vec![Point::zero()..Point::zero()],
24082                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24083            );
24084        })
24085    });
24086}
24087
24088#[gpui::test]
24089async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24090    init_test(cx, |_| {});
24091
24092    let fs = FakeFs::new(cx.executor());
24093    let main_text = r#"fn main() {
24094println!("1");
24095println!("2");
24096println!("3");
24097println!("4");
24098println!("5");
24099}"#;
24100    let lib_text = "mod foo {}";
24101    fs.insert_tree(
24102        path!("/a"),
24103        json!({
24104            "lib.rs": lib_text,
24105            "main.rs": main_text,
24106        }),
24107    )
24108    .await;
24109
24110    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24111    let (workspace, cx) =
24112        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24113    let worktree_id = workspace.update(cx, |workspace, cx| {
24114        workspace.project().update(cx, |project, cx| {
24115            project.worktrees(cx).next().unwrap().read(cx).id()
24116        })
24117    });
24118
24119    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24120    let editor = workspace
24121        .update_in(cx, |workspace, window, cx| {
24122            workspace.open_path(
24123                (worktree_id, rel_path("main.rs")),
24124                Some(pane.downgrade()),
24125                true,
24126                window,
24127                cx,
24128            )
24129        })
24130        .unwrap()
24131        .await
24132        .downcast::<Editor>()
24133        .unwrap();
24134    pane.update(cx, |pane, cx| {
24135        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24136        open_editor.update(cx, |editor, cx| {
24137            assert_eq!(
24138                editor.display_text(cx),
24139                main_text,
24140                "Original main.rs text on initial open",
24141            );
24142        })
24143    });
24144    editor.update_in(cx, |editor, window, cx| {
24145        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24146    });
24147
24148    cx.update_global(|store: &mut SettingsStore, cx| {
24149        store.update_user_settings(cx, |s| {
24150            s.workspace.restore_on_file_reopen = Some(false);
24151        });
24152    });
24153    editor.update_in(cx, |editor, window, cx| {
24154        editor.fold_ranges(
24155            vec![
24156                Point::new(1, 0)..Point::new(1, 1),
24157                Point::new(2, 0)..Point::new(2, 2),
24158                Point::new(3, 0)..Point::new(3, 3),
24159            ],
24160            false,
24161            window,
24162            cx,
24163        );
24164    });
24165    pane.update_in(cx, |pane, window, cx| {
24166        pane.close_all_items(&CloseAllItems::default(), window, cx)
24167    })
24168    .await
24169    .unwrap();
24170    pane.update(cx, |pane, _| {
24171        assert!(pane.active_item().is_none());
24172    });
24173    cx.update_global(|store: &mut SettingsStore, cx| {
24174        store.update_user_settings(cx, |s| {
24175            s.workspace.restore_on_file_reopen = Some(true);
24176        });
24177    });
24178
24179    let _editor_reopened = workspace
24180        .update_in(cx, |workspace, window, cx| {
24181            workspace.open_path(
24182                (worktree_id, rel_path("main.rs")),
24183                Some(pane.downgrade()),
24184                true,
24185                window,
24186                cx,
24187            )
24188        })
24189        .unwrap()
24190        .await
24191        .downcast::<Editor>()
24192        .unwrap();
24193    pane.update(cx, |pane, cx| {
24194        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24195        open_editor.update(cx, |editor, cx| {
24196            assert_eq!(
24197                editor.display_text(cx),
24198                main_text,
24199                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24200            );
24201        })
24202    });
24203}
24204
24205#[gpui::test]
24206async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24207    struct EmptyModalView {
24208        focus_handle: gpui::FocusHandle,
24209    }
24210    impl EventEmitter<DismissEvent> for EmptyModalView {}
24211    impl Render for EmptyModalView {
24212        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24213            div()
24214        }
24215    }
24216    impl Focusable for EmptyModalView {
24217        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24218            self.focus_handle.clone()
24219        }
24220    }
24221    impl workspace::ModalView for EmptyModalView {}
24222    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24223        EmptyModalView {
24224            focus_handle: cx.focus_handle(),
24225        }
24226    }
24227
24228    init_test(cx, |_| {});
24229
24230    let fs = FakeFs::new(cx.executor());
24231    let project = Project::test(fs, [], cx).await;
24232    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24233    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24234    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24235    let editor = cx.new_window_entity(|window, cx| {
24236        Editor::new(
24237            EditorMode::full(),
24238            buffer,
24239            Some(project.clone()),
24240            window,
24241            cx,
24242        )
24243    });
24244    workspace
24245        .update(cx, |workspace, window, cx| {
24246            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24247        })
24248        .unwrap();
24249    editor.update_in(cx, |editor, window, cx| {
24250        editor.open_context_menu(&OpenContextMenu, window, cx);
24251        assert!(editor.mouse_context_menu.is_some());
24252    });
24253    workspace
24254        .update(cx, |workspace, window, cx| {
24255            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24256        })
24257        .unwrap();
24258    cx.read(|cx| {
24259        assert!(editor.read(cx).mouse_context_menu.is_none());
24260    });
24261}
24262
24263fn set_linked_edit_ranges(
24264    opening: (Point, Point),
24265    closing: (Point, Point),
24266    editor: &mut Editor,
24267    cx: &mut Context<Editor>,
24268) {
24269    let Some((buffer, _)) = editor
24270        .buffer
24271        .read(cx)
24272        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24273    else {
24274        panic!("Failed to get buffer for selection position");
24275    };
24276    let buffer = buffer.read(cx);
24277    let buffer_id = buffer.remote_id();
24278    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24279    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24280    let mut linked_ranges = HashMap::default();
24281    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24282    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24283}
24284
24285#[gpui::test]
24286async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24287    init_test(cx, |_| {});
24288
24289    let fs = FakeFs::new(cx.executor());
24290    fs.insert_file(path!("/file.html"), Default::default())
24291        .await;
24292
24293    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24294
24295    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24296    let html_language = Arc::new(Language::new(
24297        LanguageConfig {
24298            name: "HTML".into(),
24299            matcher: LanguageMatcher {
24300                path_suffixes: vec!["html".to_string()],
24301                ..LanguageMatcher::default()
24302            },
24303            brackets: BracketPairConfig {
24304                pairs: vec![BracketPair {
24305                    start: "<".into(),
24306                    end: ">".into(),
24307                    close: true,
24308                    ..Default::default()
24309                }],
24310                ..Default::default()
24311            },
24312            ..Default::default()
24313        },
24314        Some(tree_sitter_html::LANGUAGE.into()),
24315    ));
24316    language_registry.add(html_language);
24317    let mut fake_servers = language_registry.register_fake_lsp(
24318        "HTML",
24319        FakeLspAdapter {
24320            capabilities: lsp::ServerCapabilities {
24321                completion_provider: Some(lsp::CompletionOptions {
24322                    resolve_provider: Some(true),
24323                    ..Default::default()
24324                }),
24325                ..Default::default()
24326            },
24327            ..Default::default()
24328        },
24329    );
24330
24331    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24332    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24333
24334    let worktree_id = workspace
24335        .update(cx, |workspace, _window, cx| {
24336            workspace.project().update(cx, |project, cx| {
24337                project.worktrees(cx).next().unwrap().read(cx).id()
24338            })
24339        })
24340        .unwrap();
24341    project
24342        .update(cx, |project, cx| {
24343            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24344        })
24345        .await
24346        .unwrap();
24347    let editor = workspace
24348        .update(cx, |workspace, window, cx| {
24349            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24350        })
24351        .unwrap()
24352        .await
24353        .unwrap()
24354        .downcast::<Editor>()
24355        .unwrap();
24356
24357    let fake_server = fake_servers.next().await.unwrap();
24358    editor.update_in(cx, |editor, window, cx| {
24359        editor.set_text("<ad></ad>", window, cx);
24360        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24361            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24362        });
24363        set_linked_edit_ranges(
24364            (Point::new(0, 1), Point::new(0, 3)),
24365            (Point::new(0, 6), Point::new(0, 8)),
24366            editor,
24367            cx,
24368        );
24369    });
24370    let mut completion_handle =
24371        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24372            Ok(Some(lsp::CompletionResponse::Array(vec![
24373                lsp::CompletionItem {
24374                    label: "head".to_string(),
24375                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24376                        lsp::InsertReplaceEdit {
24377                            new_text: "head".to_string(),
24378                            insert: lsp::Range::new(
24379                                lsp::Position::new(0, 1),
24380                                lsp::Position::new(0, 3),
24381                            ),
24382                            replace: lsp::Range::new(
24383                                lsp::Position::new(0, 1),
24384                                lsp::Position::new(0, 3),
24385                            ),
24386                        },
24387                    )),
24388                    ..Default::default()
24389                },
24390            ])))
24391        });
24392    editor.update_in(cx, |editor, window, cx| {
24393        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24394    });
24395    cx.run_until_parked();
24396    completion_handle.next().await.unwrap();
24397    editor.update(cx, |editor, _| {
24398        assert!(
24399            editor.context_menu_visible(),
24400            "Completion menu should be visible"
24401        );
24402    });
24403    editor.update_in(cx, |editor, window, cx| {
24404        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24405    });
24406    cx.executor().run_until_parked();
24407    editor.update(cx, |editor, cx| {
24408        assert_eq!(editor.text(cx), "<head></head>");
24409    });
24410}
24411
24412#[gpui::test]
24413async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24414    init_test(cx, |_| {});
24415
24416    let mut cx = EditorTestContext::new(cx).await;
24417    let language = Arc::new(Language::new(
24418        LanguageConfig {
24419            name: "TSX".into(),
24420            matcher: LanguageMatcher {
24421                path_suffixes: vec!["tsx".to_string()],
24422                ..LanguageMatcher::default()
24423            },
24424            brackets: BracketPairConfig {
24425                pairs: vec![BracketPair {
24426                    start: "<".into(),
24427                    end: ">".into(),
24428                    close: true,
24429                    ..Default::default()
24430                }],
24431                ..Default::default()
24432            },
24433            linked_edit_characters: HashSet::from_iter(['.']),
24434            ..Default::default()
24435        },
24436        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24437    ));
24438    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24439
24440    // Test typing > does not extend linked pair
24441    cx.set_state("<divˇ<div></div>");
24442    cx.update_editor(|editor, _, cx| {
24443        set_linked_edit_ranges(
24444            (Point::new(0, 1), Point::new(0, 4)),
24445            (Point::new(0, 11), Point::new(0, 14)),
24446            editor,
24447            cx,
24448        );
24449    });
24450    cx.update_editor(|editor, window, cx| {
24451        editor.handle_input(">", window, cx);
24452    });
24453    cx.assert_editor_state("<div>ˇ<div></div>");
24454
24455    // Test typing . do extend linked pair
24456    cx.set_state("<Animatedˇ></Animated>");
24457    cx.update_editor(|editor, _, cx| {
24458        set_linked_edit_ranges(
24459            (Point::new(0, 1), Point::new(0, 9)),
24460            (Point::new(0, 12), Point::new(0, 20)),
24461            editor,
24462            cx,
24463        );
24464    });
24465    cx.update_editor(|editor, window, cx| {
24466        editor.handle_input(".", window, cx);
24467    });
24468    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24469    cx.update_editor(|editor, _, cx| {
24470        set_linked_edit_ranges(
24471            (Point::new(0, 1), Point::new(0, 10)),
24472            (Point::new(0, 13), Point::new(0, 21)),
24473            editor,
24474            cx,
24475        );
24476    });
24477    cx.update_editor(|editor, window, cx| {
24478        editor.handle_input("V", window, cx);
24479    });
24480    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24481}
24482
24483#[gpui::test]
24484async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24485    init_test(cx, |_| {});
24486
24487    let fs = FakeFs::new(cx.executor());
24488    fs.insert_tree(
24489        path!("/root"),
24490        json!({
24491            "a": {
24492                "main.rs": "fn main() {}",
24493            },
24494            "foo": {
24495                "bar": {
24496                    "external_file.rs": "pub mod external {}",
24497                }
24498            }
24499        }),
24500    )
24501    .await;
24502
24503    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24504    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24505    language_registry.add(rust_lang());
24506    let _fake_servers = language_registry.register_fake_lsp(
24507        "Rust",
24508        FakeLspAdapter {
24509            ..FakeLspAdapter::default()
24510        },
24511    );
24512    let (workspace, cx) =
24513        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24514    let worktree_id = workspace.update(cx, |workspace, cx| {
24515        workspace.project().update(cx, |project, cx| {
24516            project.worktrees(cx).next().unwrap().read(cx).id()
24517        })
24518    });
24519
24520    let assert_language_servers_count =
24521        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24522            project.update(cx, |project, cx| {
24523                let current = project
24524                    .lsp_store()
24525                    .read(cx)
24526                    .as_local()
24527                    .unwrap()
24528                    .language_servers
24529                    .len();
24530                assert_eq!(expected, current, "{context}");
24531            });
24532        };
24533
24534    assert_language_servers_count(
24535        0,
24536        "No servers should be running before any file is open",
24537        cx,
24538    );
24539    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24540    let main_editor = workspace
24541        .update_in(cx, |workspace, window, cx| {
24542            workspace.open_path(
24543                (worktree_id, rel_path("main.rs")),
24544                Some(pane.downgrade()),
24545                true,
24546                window,
24547                cx,
24548            )
24549        })
24550        .unwrap()
24551        .await
24552        .downcast::<Editor>()
24553        .unwrap();
24554    pane.update(cx, |pane, cx| {
24555        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24556        open_editor.update(cx, |editor, cx| {
24557            assert_eq!(
24558                editor.display_text(cx),
24559                "fn main() {}",
24560                "Original main.rs text on initial open",
24561            );
24562        });
24563        assert_eq!(open_editor, main_editor);
24564    });
24565    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24566
24567    let external_editor = workspace
24568        .update_in(cx, |workspace, window, cx| {
24569            workspace.open_abs_path(
24570                PathBuf::from("/root/foo/bar/external_file.rs"),
24571                OpenOptions::default(),
24572                window,
24573                cx,
24574            )
24575        })
24576        .await
24577        .expect("opening external file")
24578        .downcast::<Editor>()
24579        .expect("downcasted external file's open element to editor");
24580    pane.update(cx, |pane, cx| {
24581        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24582        open_editor.update(cx, |editor, cx| {
24583            assert_eq!(
24584                editor.display_text(cx),
24585                "pub mod external {}",
24586                "External file is open now",
24587            );
24588        });
24589        assert_eq!(open_editor, external_editor);
24590    });
24591    assert_language_servers_count(
24592        1,
24593        "Second, external, *.rs file should join the existing server",
24594        cx,
24595    );
24596
24597    pane.update_in(cx, |pane, window, cx| {
24598        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24599    })
24600    .await
24601    .unwrap();
24602    pane.update_in(cx, |pane, window, cx| {
24603        pane.navigate_backward(&Default::default(), window, cx);
24604    });
24605    cx.run_until_parked();
24606    pane.update(cx, |pane, cx| {
24607        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24608        open_editor.update(cx, |editor, cx| {
24609            assert_eq!(
24610                editor.display_text(cx),
24611                "pub mod external {}",
24612                "External file is open now",
24613            );
24614        });
24615    });
24616    assert_language_servers_count(
24617        1,
24618        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24619        cx,
24620    );
24621
24622    cx.update(|_, cx| {
24623        workspace::reload(cx);
24624    });
24625    assert_language_servers_count(
24626        1,
24627        "After reloading the worktree with local and external files opened, only one project should be started",
24628        cx,
24629    );
24630}
24631
24632#[gpui::test]
24633async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24634    init_test(cx, |_| {});
24635
24636    let mut cx = EditorTestContext::new(cx).await;
24637    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24638    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24639
24640    // test cursor move to start of each line on tab
24641    // for `if`, `elif`, `else`, `while`, `with` and `for`
24642    cx.set_state(indoc! {"
24643        def main():
24644        ˇ    for item in items:
24645        ˇ        while item.active:
24646        ˇ            if item.value > 10:
24647        ˇ                continue
24648        ˇ            elif item.value < 0:
24649        ˇ                break
24650        ˇ            else:
24651        ˇ                with item.context() as ctx:
24652        ˇ                    yield count
24653        ˇ        else:
24654        ˇ            log('while else')
24655        ˇ    else:
24656        ˇ        log('for else')
24657    "});
24658    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24659    cx.assert_editor_state(indoc! {"
24660        def main():
24661            ˇfor item in items:
24662                ˇwhile item.active:
24663                    ˇif item.value > 10:
24664                        ˇcontinue
24665                    ˇelif item.value < 0:
24666                        ˇbreak
24667                    ˇelse:
24668                        ˇwith item.context() as ctx:
24669                            ˇyield count
24670                ˇelse:
24671                    ˇlog('while else')
24672            ˇelse:
24673                ˇlog('for else')
24674    "});
24675    // test relative indent is preserved when tab
24676    // for `if`, `elif`, `else`, `while`, `with` and `for`
24677    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24678    cx.assert_editor_state(indoc! {"
24679        def main():
24680                ˇfor item in items:
24681                    ˇwhile item.active:
24682                        ˇif item.value > 10:
24683                            ˇcontinue
24684                        ˇelif item.value < 0:
24685                            ˇbreak
24686                        ˇelse:
24687                            ˇwith item.context() as ctx:
24688                                ˇyield count
24689                    ˇelse:
24690                        ˇlog('while else')
24691                ˇelse:
24692                    ˇlog('for else')
24693    "});
24694
24695    // test cursor move to start of each line on tab
24696    // for `try`, `except`, `else`, `finally`, `match` and `def`
24697    cx.set_state(indoc! {"
24698        def main():
24699        ˇ    try:
24700        ˇ        fetch()
24701        ˇ    except ValueError:
24702        ˇ        handle_error()
24703        ˇ    else:
24704        ˇ        match value:
24705        ˇ            case _:
24706        ˇ    finally:
24707        ˇ        def status():
24708        ˇ            return 0
24709    "});
24710    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24711    cx.assert_editor_state(indoc! {"
24712        def main():
24713            ˇtry:
24714                ˇfetch()
24715            ˇexcept ValueError:
24716                ˇhandle_error()
24717            ˇelse:
24718                ˇmatch value:
24719                    ˇcase _:
24720            ˇfinally:
24721                ˇdef status():
24722                    ˇreturn 0
24723    "});
24724    // test relative indent is preserved when tab
24725    // for `try`, `except`, `else`, `finally`, `match` and `def`
24726    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24727    cx.assert_editor_state(indoc! {"
24728        def main():
24729                ˇtry:
24730                    ˇfetch()
24731                ˇexcept ValueError:
24732                    ˇhandle_error()
24733                ˇelse:
24734                    ˇmatch value:
24735                        ˇcase _:
24736                ˇfinally:
24737                    ˇdef status():
24738                        ˇreturn 0
24739    "});
24740}
24741
24742#[gpui::test]
24743async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24744    init_test(cx, |_| {});
24745
24746    let mut cx = EditorTestContext::new(cx).await;
24747    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24748    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24749
24750    // test `else` auto outdents when typed inside `if` block
24751    cx.set_state(indoc! {"
24752        def main():
24753            if i == 2:
24754                return
24755                ˇ
24756    "});
24757    cx.update_editor(|editor, window, cx| {
24758        editor.handle_input("else:", window, cx);
24759    });
24760    cx.assert_editor_state(indoc! {"
24761        def main():
24762            if i == 2:
24763                return
24764            else:ˇ
24765    "});
24766
24767    // test `except` auto outdents when typed inside `try` block
24768    cx.set_state(indoc! {"
24769        def main():
24770            try:
24771                i = 2
24772                ˇ
24773    "});
24774    cx.update_editor(|editor, window, cx| {
24775        editor.handle_input("except:", window, cx);
24776    });
24777    cx.assert_editor_state(indoc! {"
24778        def main():
24779            try:
24780                i = 2
24781            except:ˇ
24782    "});
24783
24784    // test `else` auto outdents when typed inside `except` block
24785    cx.set_state(indoc! {"
24786        def main():
24787            try:
24788                i = 2
24789            except:
24790                j = 2
24791                ˇ
24792    "});
24793    cx.update_editor(|editor, window, cx| {
24794        editor.handle_input("else:", window, cx);
24795    });
24796    cx.assert_editor_state(indoc! {"
24797        def main():
24798            try:
24799                i = 2
24800            except:
24801                j = 2
24802            else:ˇ
24803    "});
24804
24805    // test `finally` auto outdents when typed inside `else` block
24806    cx.set_state(indoc! {"
24807        def main():
24808            try:
24809                i = 2
24810            except:
24811                j = 2
24812            else:
24813                k = 2
24814                ˇ
24815    "});
24816    cx.update_editor(|editor, window, cx| {
24817        editor.handle_input("finally:", window, cx);
24818    });
24819    cx.assert_editor_state(indoc! {"
24820        def main():
24821            try:
24822                i = 2
24823            except:
24824                j = 2
24825            else:
24826                k = 2
24827            finally:ˇ
24828    "});
24829
24830    // test `else` does not outdents when typed inside `except` block right after for block
24831    cx.set_state(indoc! {"
24832        def main():
24833            try:
24834                i = 2
24835            except:
24836                for i in range(n):
24837                    pass
24838                ˇ
24839    "});
24840    cx.update_editor(|editor, window, cx| {
24841        editor.handle_input("else:", window, cx);
24842    });
24843    cx.assert_editor_state(indoc! {"
24844        def main():
24845            try:
24846                i = 2
24847            except:
24848                for i in range(n):
24849                    pass
24850                else:ˇ
24851    "});
24852
24853    // test `finally` auto outdents when typed inside `else` block right after for block
24854    cx.set_state(indoc! {"
24855        def main():
24856            try:
24857                i = 2
24858            except:
24859                j = 2
24860            else:
24861                for i in range(n):
24862                    pass
24863                ˇ
24864    "});
24865    cx.update_editor(|editor, window, cx| {
24866        editor.handle_input("finally:", window, cx);
24867    });
24868    cx.assert_editor_state(indoc! {"
24869        def main():
24870            try:
24871                i = 2
24872            except:
24873                j = 2
24874            else:
24875                for i in range(n):
24876                    pass
24877            finally:ˇ
24878    "});
24879
24880    // test `except` outdents to inner "try" block
24881    cx.set_state(indoc! {"
24882        def main():
24883            try:
24884                i = 2
24885                if i == 2:
24886                    try:
24887                        i = 3
24888                        ˇ
24889    "});
24890    cx.update_editor(|editor, window, cx| {
24891        editor.handle_input("except:", window, cx);
24892    });
24893    cx.assert_editor_state(indoc! {"
24894        def main():
24895            try:
24896                i = 2
24897                if i == 2:
24898                    try:
24899                        i = 3
24900                    except:ˇ
24901    "});
24902
24903    // test `except` outdents to outer "try" block
24904    cx.set_state(indoc! {"
24905        def main():
24906            try:
24907                i = 2
24908                if i == 2:
24909                    try:
24910                        i = 3
24911                ˇ
24912    "});
24913    cx.update_editor(|editor, window, cx| {
24914        editor.handle_input("except:", window, cx);
24915    });
24916    cx.assert_editor_state(indoc! {"
24917        def main():
24918            try:
24919                i = 2
24920                if i == 2:
24921                    try:
24922                        i = 3
24923            except:ˇ
24924    "});
24925
24926    // test `else` stays at correct indent when typed after `for` block
24927    cx.set_state(indoc! {"
24928        def main():
24929            for i in range(10):
24930                if i == 3:
24931                    break
24932            ˇ
24933    "});
24934    cx.update_editor(|editor, window, cx| {
24935        editor.handle_input("else:", window, cx);
24936    });
24937    cx.assert_editor_state(indoc! {"
24938        def main():
24939            for i in range(10):
24940                if i == 3:
24941                    break
24942            else:ˇ
24943    "});
24944
24945    // test does not outdent on typing after line with square brackets
24946    cx.set_state(indoc! {"
24947        def f() -> list[str]:
24948            ˇ
24949    "});
24950    cx.update_editor(|editor, window, cx| {
24951        editor.handle_input("a", window, cx);
24952    });
24953    cx.assert_editor_state(indoc! {"
24954        def f() -> list[str]:
2495524956    "});
24957
24958    // test does not outdent on typing : after case keyword
24959    cx.set_state(indoc! {"
24960        match 1:
24961            caseˇ
24962    "});
24963    cx.update_editor(|editor, window, cx| {
24964        editor.handle_input(":", window, cx);
24965    });
24966    cx.assert_editor_state(indoc! {"
24967        match 1:
24968            case:ˇ
24969    "});
24970}
24971
24972#[gpui::test]
24973async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24974    init_test(cx, |_| {});
24975    update_test_language_settings(cx, |settings| {
24976        settings.defaults.extend_comment_on_newline = Some(false);
24977    });
24978    let mut cx = EditorTestContext::new(cx).await;
24979    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24980    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24981
24982    // test correct indent after newline on comment
24983    cx.set_state(indoc! {"
24984        # COMMENT:ˇ
24985    "});
24986    cx.update_editor(|editor, window, cx| {
24987        editor.newline(&Newline, window, cx);
24988    });
24989    cx.assert_editor_state(indoc! {"
24990        # COMMENT:
24991        ˇ
24992    "});
24993
24994    // test correct indent after newline in brackets
24995    cx.set_state(indoc! {"
24996        {ˇ}
24997    "});
24998    cx.update_editor(|editor, window, cx| {
24999        editor.newline(&Newline, window, cx);
25000    });
25001    cx.run_until_parked();
25002    cx.assert_editor_state(indoc! {"
25003        {
25004            ˇ
25005        }
25006    "});
25007
25008    cx.set_state(indoc! {"
25009        (ˇ)
25010    "});
25011    cx.update_editor(|editor, window, cx| {
25012        editor.newline(&Newline, window, cx);
25013    });
25014    cx.run_until_parked();
25015    cx.assert_editor_state(indoc! {"
25016        (
25017            ˇ
25018        )
25019    "});
25020
25021    // do not indent after empty lists or dictionaries
25022    cx.set_state(indoc! {"
25023        a = []ˇ
25024    "});
25025    cx.update_editor(|editor, window, cx| {
25026        editor.newline(&Newline, window, cx);
25027    });
25028    cx.run_until_parked();
25029    cx.assert_editor_state(indoc! {"
25030        a = []
25031        ˇ
25032    "});
25033}
25034
25035#[gpui::test]
25036async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25037    init_test(cx, |_| {});
25038
25039    let mut cx = EditorTestContext::new(cx).await;
25040    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25041    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25042
25043    // test cursor move to start of each line on tab
25044    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25045    cx.set_state(indoc! {"
25046        function main() {
25047        ˇ    for item in $items; do
25048        ˇ        while [ -n \"$item\" ]; do
25049        ˇ            if [ \"$value\" -gt 10 ]; then
25050        ˇ                continue
25051        ˇ            elif [ \"$value\" -lt 0 ]; then
25052        ˇ                break
25053        ˇ            else
25054        ˇ                echo \"$item\"
25055        ˇ            fi
25056        ˇ        done
25057        ˇ    done
25058        ˇ}
25059    "});
25060    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25061    cx.assert_editor_state(indoc! {"
25062        function main() {
25063            ˇfor item in $items; do
25064                ˇwhile [ -n \"$item\" ]; do
25065                    ˇif [ \"$value\" -gt 10 ]; then
25066                        ˇcontinue
25067                    ˇelif [ \"$value\" -lt 0 ]; then
25068                        ˇbreak
25069                    ˇelse
25070                        ˇecho \"$item\"
25071                    ˇfi
25072                ˇdone
25073            ˇdone
25074        ˇ}
25075    "});
25076    // test relative indent is preserved when tab
25077    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25078    cx.assert_editor_state(indoc! {"
25079        function main() {
25080                ˇfor item in $items; do
25081                    ˇwhile [ -n \"$item\" ]; do
25082                        ˇif [ \"$value\" -gt 10 ]; then
25083                            ˇcontinue
25084                        ˇelif [ \"$value\" -lt 0 ]; then
25085                            ˇbreak
25086                        ˇelse
25087                            ˇecho \"$item\"
25088                        ˇfi
25089                    ˇdone
25090                ˇdone
25091            ˇ}
25092    "});
25093
25094    // test cursor move to start of each line on tab
25095    // for `case` statement with patterns
25096    cx.set_state(indoc! {"
25097        function handle() {
25098        ˇ    case \"$1\" in
25099        ˇ        start)
25100        ˇ            echo \"a\"
25101        ˇ            ;;
25102        ˇ        stop)
25103        ˇ            echo \"b\"
25104        ˇ            ;;
25105        ˇ        *)
25106        ˇ            echo \"c\"
25107        ˇ            ;;
25108        ˇ    esac
25109        ˇ}
25110    "});
25111    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25112    cx.assert_editor_state(indoc! {"
25113        function handle() {
25114            ˇcase \"$1\" in
25115                ˇstart)
25116                    ˇecho \"a\"
25117                    ˇ;;
25118                ˇstop)
25119                    ˇecho \"b\"
25120                    ˇ;;
25121                ˇ*)
25122                    ˇecho \"c\"
25123                    ˇ;;
25124            ˇesac
25125        ˇ}
25126    "});
25127}
25128
25129#[gpui::test]
25130async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25131    init_test(cx, |_| {});
25132
25133    let mut cx = EditorTestContext::new(cx).await;
25134    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25135    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25136
25137    // test indents on comment insert
25138    cx.set_state(indoc! {"
25139        function main() {
25140        ˇ    for item in $items; do
25141        ˇ        while [ -n \"$item\" ]; do
25142        ˇ            if [ \"$value\" -gt 10 ]; then
25143        ˇ                continue
25144        ˇ            elif [ \"$value\" -lt 0 ]; then
25145        ˇ                break
25146        ˇ            else
25147        ˇ                echo \"$item\"
25148        ˇ            fi
25149        ˇ        done
25150        ˇ    done
25151        ˇ}
25152    "});
25153    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25154    cx.assert_editor_state(indoc! {"
25155        function main() {
25156        #ˇ    for item in $items; do
25157        #ˇ        while [ -n \"$item\" ]; do
25158        #ˇ            if [ \"$value\" -gt 10 ]; then
25159        #ˇ                continue
25160        #ˇ            elif [ \"$value\" -lt 0 ]; then
25161        #ˇ                break
25162        #ˇ            else
25163        #ˇ                echo \"$item\"
25164        #ˇ            fi
25165        #ˇ        done
25166        #ˇ    done
25167        #ˇ}
25168    "});
25169}
25170
25171#[gpui::test]
25172async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25173    init_test(cx, |_| {});
25174
25175    let mut cx = EditorTestContext::new(cx).await;
25176    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25177    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25178
25179    // test `else` auto outdents when typed inside `if` block
25180    cx.set_state(indoc! {"
25181        if [ \"$1\" = \"test\" ]; then
25182            echo \"foo bar\"
25183            ˇ
25184    "});
25185    cx.update_editor(|editor, window, cx| {
25186        editor.handle_input("else", window, cx);
25187    });
25188    cx.assert_editor_state(indoc! {"
25189        if [ \"$1\" = \"test\" ]; then
25190            echo \"foo bar\"
25191        elseˇ
25192    "});
25193
25194    // test `elif` auto outdents when typed inside `if` block
25195    cx.set_state(indoc! {"
25196        if [ \"$1\" = \"test\" ]; then
25197            echo \"foo bar\"
25198            ˇ
25199    "});
25200    cx.update_editor(|editor, window, cx| {
25201        editor.handle_input("elif", window, cx);
25202    });
25203    cx.assert_editor_state(indoc! {"
25204        if [ \"$1\" = \"test\" ]; then
25205            echo \"foo bar\"
25206        elifˇ
25207    "});
25208
25209    // test `fi` auto outdents when typed inside `else` block
25210    cx.set_state(indoc! {"
25211        if [ \"$1\" = \"test\" ]; then
25212            echo \"foo bar\"
25213        else
25214            echo \"bar baz\"
25215            ˇ
25216    "});
25217    cx.update_editor(|editor, window, cx| {
25218        editor.handle_input("fi", window, cx);
25219    });
25220    cx.assert_editor_state(indoc! {"
25221        if [ \"$1\" = \"test\" ]; then
25222            echo \"foo bar\"
25223        else
25224            echo \"bar baz\"
25225        fiˇ
25226    "});
25227
25228    // test `done` auto outdents when typed inside `while` block
25229    cx.set_state(indoc! {"
25230        while read line; do
25231            echo \"$line\"
25232            ˇ
25233    "});
25234    cx.update_editor(|editor, window, cx| {
25235        editor.handle_input("done", window, cx);
25236    });
25237    cx.assert_editor_state(indoc! {"
25238        while read line; do
25239            echo \"$line\"
25240        doneˇ
25241    "});
25242
25243    // test `done` auto outdents when typed inside `for` block
25244    cx.set_state(indoc! {"
25245        for file in *.txt; do
25246            cat \"$file\"
25247            ˇ
25248    "});
25249    cx.update_editor(|editor, window, cx| {
25250        editor.handle_input("done", window, cx);
25251    });
25252    cx.assert_editor_state(indoc! {"
25253        for file in *.txt; do
25254            cat \"$file\"
25255        doneˇ
25256    "});
25257
25258    // test `esac` auto outdents when typed inside `case` block
25259    cx.set_state(indoc! {"
25260        case \"$1\" in
25261            start)
25262                echo \"foo bar\"
25263                ;;
25264            stop)
25265                echo \"bar baz\"
25266                ;;
25267            ˇ
25268    "});
25269    cx.update_editor(|editor, window, cx| {
25270        editor.handle_input("esac", window, cx);
25271    });
25272    cx.assert_editor_state(indoc! {"
25273        case \"$1\" in
25274            start)
25275                echo \"foo bar\"
25276                ;;
25277            stop)
25278                echo \"bar baz\"
25279                ;;
25280        esacˇ
25281    "});
25282
25283    // test `*)` auto outdents when typed inside `case` block
25284    cx.set_state(indoc! {"
25285        case \"$1\" in
25286            start)
25287                echo \"foo bar\"
25288                ;;
25289                ˇ
25290    "});
25291    cx.update_editor(|editor, window, cx| {
25292        editor.handle_input("*)", window, cx);
25293    });
25294    cx.assert_editor_state(indoc! {"
25295        case \"$1\" in
25296            start)
25297                echo \"foo bar\"
25298                ;;
25299            *)ˇ
25300    "});
25301
25302    // test `fi` outdents to correct level with nested if blocks
25303    cx.set_state(indoc! {"
25304        if [ \"$1\" = \"test\" ]; then
25305            echo \"outer if\"
25306            if [ \"$2\" = \"debug\" ]; then
25307                echo \"inner if\"
25308                ˇ
25309    "});
25310    cx.update_editor(|editor, window, cx| {
25311        editor.handle_input("fi", window, cx);
25312    });
25313    cx.assert_editor_state(indoc! {"
25314        if [ \"$1\" = \"test\" ]; then
25315            echo \"outer if\"
25316            if [ \"$2\" = \"debug\" ]; then
25317                echo \"inner if\"
25318            fiˇ
25319    "});
25320}
25321
25322#[gpui::test]
25323async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25324    init_test(cx, |_| {});
25325    update_test_language_settings(cx, |settings| {
25326        settings.defaults.extend_comment_on_newline = Some(false);
25327    });
25328    let mut cx = EditorTestContext::new(cx).await;
25329    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25330    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25331
25332    // test correct indent after newline on comment
25333    cx.set_state(indoc! {"
25334        # COMMENT:ˇ
25335    "});
25336    cx.update_editor(|editor, window, cx| {
25337        editor.newline(&Newline, window, cx);
25338    });
25339    cx.assert_editor_state(indoc! {"
25340        # COMMENT:
25341        ˇ
25342    "});
25343
25344    // test correct indent after newline after `then`
25345    cx.set_state(indoc! {"
25346
25347        if [ \"$1\" = \"test\" ]; thenˇ
25348    "});
25349    cx.update_editor(|editor, window, cx| {
25350        editor.newline(&Newline, window, cx);
25351    });
25352    cx.run_until_parked();
25353    cx.assert_editor_state(indoc! {"
25354
25355        if [ \"$1\" = \"test\" ]; then
25356            ˇ
25357    "});
25358
25359    // test correct indent after newline after `else`
25360    cx.set_state(indoc! {"
25361        if [ \"$1\" = \"test\" ]; then
25362        elseˇ
25363    "});
25364    cx.update_editor(|editor, window, cx| {
25365        editor.newline(&Newline, window, cx);
25366    });
25367    cx.run_until_parked();
25368    cx.assert_editor_state(indoc! {"
25369        if [ \"$1\" = \"test\" ]; then
25370        else
25371            ˇ
25372    "});
25373
25374    // test correct indent after newline after `elif`
25375    cx.set_state(indoc! {"
25376        if [ \"$1\" = \"test\" ]; then
25377        elifˇ
25378    "});
25379    cx.update_editor(|editor, window, cx| {
25380        editor.newline(&Newline, window, cx);
25381    });
25382    cx.run_until_parked();
25383    cx.assert_editor_state(indoc! {"
25384        if [ \"$1\" = \"test\" ]; then
25385        elif
25386            ˇ
25387    "});
25388
25389    // test correct indent after newline after `do`
25390    cx.set_state(indoc! {"
25391        for file in *.txt; doˇ
25392    "});
25393    cx.update_editor(|editor, window, cx| {
25394        editor.newline(&Newline, window, cx);
25395    });
25396    cx.run_until_parked();
25397    cx.assert_editor_state(indoc! {"
25398        for file in *.txt; do
25399            ˇ
25400    "});
25401
25402    // test correct indent after newline after case pattern
25403    cx.set_state(indoc! {"
25404        case \"$1\" in
25405            start)ˇ
25406    "});
25407    cx.update_editor(|editor, window, cx| {
25408        editor.newline(&Newline, window, cx);
25409    });
25410    cx.run_until_parked();
25411    cx.assert_editor_state(indoc! {"
25412        case \"$1\" in
25413            start)
25414                ˇ
25415    "});
25416
25417    // test correct indent after newline after case pattern
25418    cx.set_state(indoc! {"
25419        case \"$1\" in
25420            start)
25421                ;;
25422            *)ˇ
25423    "});
25424    cx.update_editor(|editor, window, cx| {
25425        editor.newline(&Newline, window, cx);
25426    });
25427    cx.run_until_parked();
25428    cx.assert_editor_state(indoc! {"
25429        case \"$1\" in
25430            start)
25431                ;;
25432            *)
25433                ˇ
25434    "});
25435
25436    // test correct indent after newline after function opening brace
25437    cx.set_state(indoc! {"
25438        function test() {ˇ}
25439    "});
25440    cx.update_editor(|editor, window, cx| {
25441        editor.newline(&Newline, window, cx);
25442    });
25443    cx.run_until_parked();
25444    cx.assert_editor_state(indoc! {"
25445        function test() {
25446            ˇ
25447        }
25448    "});
25449
25450    // test no extra indent after semicolon on same line
25451    cx.set_state(indoc! {"
25452        echo \"test\"25453    "});
25454    cx.update_editor(|editor, window, cx| {
25455        editor.newline(&Newline, window, cx);
25456    });
25457    cx.run_until_parked();
25458    cx.assert_editor_state(indoc! {"
25459        echo \"test\";
25460        ˇ
25461    "});
25462}
25463
25464fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25465    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25466    point..point
25467}
25468
25469#[track_caller]
25470fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25471    let (text, ranges) = marked_text_ranges(marked_text, true);
25472    assert_eq!(editor.text(cx), text);
25473    assert_eq!(
25474        editor.selections.ranges(&editor.display_snapshot(cx)),
25475        ranges,
25476        "Assert selections are {}",
25477        marked_text
25478    );
25479}
25480
25481pub fn handle_signature_help_request(
25482    cx: &mut EditorLspTestContext,
25483    mocked_response: lsp::SignatureHelp,
25484) -> impl Future<Output = ()> + use<> {
25485    let mut request =
25486        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25487            let mocked_response = mocked_response.clone();
25488            async move { Ok(Some(mocked_response)) }
25489        });
25490
25491    async move {
25492        request.next().await;
25493    }
25494}
25495
25496#[track_caller]
25497pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25498    cx.update_editor(|editor, _, _| {
25499        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25500            let entries = menu.entries.borrow();
25501            let entries = entries
25502                .iter()
25503                .map(|entry| entry.string.as_str())
25504                .collect::<Vec<_>>();
25505            assert_eq!(entries, expected);
25506        } else {
25507            panic!("Expected completions menu");
25508        }
25509    });
25510}
25511
25512/// Handle completion request passing a marked string specifying where the completion
25513/// should be triggered from using '|' character, what range should be replaced, and what completions
25514/// should be returned using '<' and '>' to delimit the range.
25515///
25516/// Also see `handle_completion_request_with_insert_and_replace`.
25517#[track_caller]
25518pub fn handle_completion_request(
25519    marked_string: &str,
25520    completions: Vec<&'static str>,
25521    is_incomplete: bool,
25522    counter: Arc<AtomicUsize>,
25523    cx: &mut EditorLspTestContext,
25524) -> impl Future<Output = ()> {
25525    let complete_from_marker: TextRangeMarker = '|'.into();
25526    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25527    let (_, mut marked_ranges) = marked_text_ranges_by(
25528        marked_string,
25529        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25530    );
25531
25532    let complete_from_position =
25533        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25534    let replace_range =
25535        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25536
25537    let mut request =
25538        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25539            let completions = completions.clone();
25540            counter.fetch_add(1, atomic::Ordering::Release);
25541            async move {
25542                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25543                assert_eq!(
25544                    params.text_document_position.position,
25545                    complete_from_position
25546                );
25547                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25548                    is_incomplete,
25549                    item_defaults: None,
25550                    items: completions
25551                        .iter()
25552                        .map(|completion_text| lsp::CompletionItem {
25553                            label: completion_text.to_string(),
25554                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25555                                range: replace_range,
25556                                new_text: completion_text.to_string(),
25557                            })),
25558                            ..Default::default()
25559                        })
25560                        .collect(),
25561                })))
25562            }
25563        });
25564
25565    async move {
25566        request.next().await;
25567    }
25568}
25569
25570/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25571/// given instead, which also contains an `insert` range.
25572///
25573/// This function uses markers to define ranges:
25574/// - `|` marks the cursor position
25575/// - `<>` marks the replace range
25576/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25577pub fn handle_completion_request_with_insert_and_replace(
25578    cx: &mut EditorLspTestContext,
25579    marked_string: &str,
25580    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25581    counter: Arc<AtomicUsize>,
25582) -> impl Future<Output = ()> {
25583    let complete_from_marker: TextRangeMarker = '|'.into();
25584    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25585    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25586
25587    let (_, mut marked_ranges) = marked_text_ranges_by(
25588        marked_string,
25589        vec![
25590            complete_from_marker.clone(),
25591            replace_range_marker.clone(),
25592            insert_range_marker.clone(),
25593        ],
25594    );
25595
25596    let complete_from_position =
25597        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25598    let replace_range =
25599        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25600
25601    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25602        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25603        _ => lsp::Range {
25604            start: replace_range.start,
25605            end: complete_from_position,
25606        },
25607    };
25608
25609    let mut request =
25610        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25611            let completions = completions.clone();
25612            counter.fetch_add(1, atomic::Ordering::Release);
25613            async move {
25614                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25615                assert_eq!(
25616                    params.text_document_position.position, complete_from_position,
25617                    "marker `|` position doesn't match",
25618                );
25619                Ok(Some(lsp::CompletionResponse::Array(
25620                    completions
25621                        .iter()
25622                        .map(|(label, new_text)| lsp::CompletionItem {
25623                            label: label.to_string(),
25624                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25625                                lsp::InsertReplaceEdit {
25626                                    insert: insert_range,
25627                                    replace: replace_range,
25628                                    new_text: new_text.to_string(),
25629                                },
25630                            )),
25631                            ..Default::default()
25632                        })
25633                        .collect(),
25634                )))
25635            }
25636        });
25637
25638    async move {
25639        request.next().await;
25640    }
25641}
25642
25643fn handle_resolve_completion_request(
25644    cx: &mut EditorLspTestContext,
25645    edits: Option<Vec<(&'static str, &'static str)>>,
25646) -> impl Future<Output = ()> {
25647    let edits = edits.map(|edits| {
25648        edits
25649            .iter()
25650            .map(|(marked_string, new_text)| {
25651                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25652                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25653                lsp::TextEdit::new(replace_range, new_text.to_string())
25654            })
25655            .collect::<Vec<_>>()
25656    });
25657
25658    let mut request =
25659        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25660            let edits = edits.clone();
25661            async move {
25662                Ok(lsp::CompletionItem {
25663                    additional_text_edits: edits,
25664                    ..Default::default()
25665                })
25666            }
25667        });
25668
25669    async move {
25670        request.next().await;
25671    }
25672}
25673
25674pub(crate) fn update_test_language_settings(
25675    cx: &mut TestAppContext,
25676    f: impl Fn(&mut AllLanguageSettingsContent),
25677) {
25678    cx.update(|cx| {
25679        SettingsStore::update_global(cx, |store, cx| {
25680            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25681        });
25682    });
25683}
25684
25685pub(crate) fn update_test_project_settings(
25686    cx: &mut TestAppContext,
25687    f: impl Fn(&mut ProjectSettingsContent),
25688) {
25689    cx.update(|cx| {
25690        SettingsStore::update_global(cx, |store, cx| {
25691            store.update_user_settings(cx, |settings| f(&mut settings.project));
25692        });
25693    });
25694}
25695
25696pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25697    cx.update(|cx| {
25698        assets::Assets.load_test_fonts(cx);
25699        let store = SettingsStore::test(cx);
25700        cx.set_global(store);
25701        theme::init(theme::LoadThemes::JustBase, cx);
25702        release_channel::init(SemanticVersion::default(), cx);
25703        crate::init(cx);
25704    });
25705    zlog::init_test();
25706    update_test_language_settings(cx, f);
25707}
25708
25709#[track_caller]
25710fn assert_hunk_revert(
25711    not_reverted_text_with_selections: &str,
25712    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25713    expected_reverted_text_with_selections: &str,
25714    base_text: &str,
25715    cx: &mut EditorLspTestContext,
25716) {
25717    cx.set_state(not_reverted_text_with_selections);
25718    cx.set_head_text(base_text);
25719    cx.executor().run_until_parked();
25720
25721    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25722        let snapshot = editor.snapshot(window, cx);
25723        let reverted_hunk_statuses = snapshot
25724            .buffer_snapshot()
25725            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25726            .map(|hunk| hunk.status().kind)
25727            .collect::<Vec<_>>();
25728
25729        editor.git_restore(&Default::default(), window, cx);
25730        reverted_hunk_statuses
25731    });
25732    cx.executor().run_until_parked();
25733    cx.assert_editor_state(expected_reverted_text_with_selections);
25734    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25735}
25736
25737#[gpui::test(iterations = 10)]
25738async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25739    init_test(cx, |_| {});
25740
25741    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25742    let counter = diagnostic_requests.clone();
25743
25744    let fs = FakeFs::new(cx.executor());
25745    fs.insert_tree(
25746        path!("/a"),
25747        json!({
25748            "first.rs": "fn main() { let a = 5; }",
25749            "second.rs": "// Test file",
25750        }),
25751    )
25752    .await;
25753
25754    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25755    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25756    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25757
25758    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25759    language_registry.add(rust_lang());
25760    let mut fake_servers = language_registry.register_fake_lsp(
25761        "Rust",
25762        FakeLspAdapter {
25763            capabilities: lsp::ServerCapabilities {
25764                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25765                    lsp::DiagnosticOptions {
25766                        identifier: None,
25767                        inter_file_dependencies: true,
25768                        workspace_diagnostics: true,
25769                        work_done_progress_options: Default::default(),
25770                    },
25771                )),
25772                ..Default::default()
25773            },
25774            ..Default::default()
25775        },
25776    );
25777
25778    let editor = workspace
25779        .update(cx, |workspace, window, cx| {
25780            workspace.open_abs_path(
25781                PathBuf::from(path!("/a/first.rs")),
25782                OpenOptions::default(),
25783                window,
25784                cx,
25785            )
25786        })
25787        .unwrap()
25788        .await
25789        .unwrap()
25790        .downcast::<Editor>()
25791        .unwrap();
25792    let fake_server = fake_servers.next().await.unwrap();
25793    let server_id = fake_server.server.server_id();
25794    let mut first_request = fake_server
25795        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25796            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25797            let result_id = Some(new_result_id.to_string());
25798            assert_eq!(
25799                params.text_document.uri,
25800                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25801            );
25802            async move {
25803                Ok(lsp::DocumentDiagnosticReportResult::Report(
25804                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25805                        related_documents: None,
25806                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25807                            items: Vec::new(),
25808                            result_id,
25809                        },
25810                    }),
25811                ))
25812            }
25813        });
25814
25815    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25816        project.update(cx, |project, cx| {
25817            let buffer_id = editor
25818                .read(cx)
25819                .buffer()
25820                .read(cx)
25821                .as_singleton()
25822                .expect("created a singleton buffer")
25823                .read(cx)
25824                .remote_id();
25825            let buffer_result_id = project
25826                .lsp_store()
25827                .read(cx)
25828                .result_id(server_id, buffer_id, cx);
25829            assert_eq!(expected, buffer_result_id);
25830        });
25831    };
25832
25833    ensure_result_id(None, cx);
25834    cx.executor().advance_clock(Duration::from_millis(60));
25835    cx.executor().run_until_parked();
25836    assert_eq!(
25837        diagnostic_requests.load(atomic::Ordering::Acquire),
25838        1,
25839        "Opening file should trigger diagnostic request"
25840    );
25841    first_request
25842        .next()
25843        .await
25844        .expect("should have sent the first diagnostics pull request");
25845    ensure_result_id(Some("1".to_string()), cx);
25846
25847    // Editing should trigger diagnostics
25848    editor.update_in(cx, |editor, window, cx| {
25849        editor.handle_input("2", window, cx)
25850    });
25851    cx.executor().advance_clock(Duration::from_millis(60));
25852    cx.executor().run_until_parked();
25853    assert_eq!(
25854        diagnostic_requests.load(atomic::Ordering::Acquire),
25855        2,
25856        "Editing should trigger diagnostic request"
25857    );
25858    ensure_result_id(Some("2".to_string()), cx);
25859
25860    // Moving cursor should not trigger diagnostic request
25861    editor.update_in(cx, |editor, window, cx| {
25862        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25863            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25864        });
25865    });
25866    cx.executor().advance_clock(Duration::from_millis(60));
25867    cx.executor().run_until_parked();
25868    assert_eq!(
25869        diagnostic_requests.load(atomic::Ordering::Acquire),
25870        2,
25871        "Cursor movement should not trigger diagnostic request"
25872    );
25873    ensure_result_id(Some("2".to_string()), cx);
25874    // Multiple rapid edits should be debounced
25875    for _ in 0..5 {
25876        editor.update_in(cx, |editor, window, cx| {
25877            editor.handle_input("x", window, cx)
25878        });
25879    }
25880    cx.executor().advance_clock(Duration::from_millis(60));
25881    cx.executor().run_until_parked();
25882
25883    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25884    assert!(
25885        final_requests <= 4,
25886        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25887    );
25888    ensure_result_id(Some(final_requests.to_string()), cx);
25889}
25890
25891#[gpui::test]
25892async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25893    // Regression test for issue #11671
25894    // Previously, adding a cursor after moving multiple cursors would reset
25895    // the cursor count instead of adding to the existing cursors.
25896    init_test(cx, |_| {});
25897    let mut cx = EditorTestContext::new(cx).await;
25898
25899    // Create a simple buffer with cursor at start
25900    cx.set_state(indoc! {"
25901        ˇaaaa
25902        bbbb
25903        cccc
25904        dddd
25905        eeee
25906        ffff
25907        gggg
25908        hhhh"});
25909
25910    // Add 2 cursors below (so we have 3 total)
25911    cx.update_editor(|editor, window, cx| {
25912        editor.add_selection_below(&Default::default(), window, cx);
25913        editor.add_selection_below(&Default::default(), window, cx);
25914    });
25915
25916    // Verify we have 3 cursors
25917    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25918    assert_eq!(
25919        initial_count, 3,
25920        "Should have 3 cursors after adding 2 below"
25921    );
25922
25923    // Move down one line
25924    cx.update_editor(|editor, window, cx| {
25925        editor.move_down(&MoveDown, window, cx);
25926    });
25927
25928    // Add another cursor below
25929    cx.update_editor(|editor, window, cx| {
25930        editor.add_selection_below(&Default::default(), window, cx);
25931    });
25932
25933    // Should now have 4 cursors (3 original + 1 new)
25934    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25935    assert_eq!(
25936        final_count, 4,
25937        "Should have 4 cursors after moving and adding another"
25938    );
25939}
25940
25941#[gpui::test]
25942async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
25943    init_test(cx, |_| {});
25944
25945    let mut cx = EditorTestContext::new(cx).await;
25946
25947    cx.set_state(indoc!(
25948        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
25949           Second line here"#
25950    ));
25951
25952    cx.update_editor(|editor, window, cx| {
25953        // Enable soft wrapping with a narrow width to force soft wrapping and
25954        // confirm that more than 2 rows are being displayed.
25955        editor.set_wrap_width(Some(100.0.into()), cx);
25956        assert!(editor.display_text(cx).lines().count() > 2);
25957
25958        editor.add_selection_below(
25959            &AddSelectionBelow {
25960                skip_soft_wrap: true,
25961            },
25962            window,
25963            cx,
25964        );
25965
25966        assert_eq!(
25967            display_ranges(editor, cx),
25968            &[
25969                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25970                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
25971            ]
25972        );
25973
25974        editor.add_selection_above(
25975            &AddSelectionAbove {
25976                skip_soft_wrap: true,
25977            },
25978            window,
25979            cx,
25980        );
25981
25982        assert_eq!(
25983            display_ranges(editor, cx),
25984            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25985        );
25986
25987        editor.add_selection_below(
25988            &AddSelectionBelow {
25989                skip_soft_wrap: false,
25990            },
25991            window,
25992            cx,
25993        );
25994
25995        assert_eq!(
25996            display_ranges(editor, cx),
25997            &[
25998                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25999                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26000            ]
26001        );
26002
26003        editor.add_selection_above(
26004            &AddSelectionAbove {
26005                skip_soft_wrap: false,
26006            },
26007            window,
26008            cx,
26009        );
26010
26011        assert_eq!(
26012            display_ranges(editor, cx),
26013            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26014        );
26015    });
26016}
26017
26018#[gpui::test(iterations = 10)]
26019async fn test_document_colors(cx: &mut TestAppContext) {
26020    let expected_color = Rgba {
26021        r: 0.33,
26022        g: 0.33,
26023        b: 0.33,
26024        a: 0.33,
26025    };
26026
26027    init_test(cx, |_| {});
26028
26029    let fs = FakeFs::new(cx.executor());
26030    fs.insert_tree(
26031        path!("/a"),
26032        json!({
26033            "first.rs": "fn main() { let a = 5; }",
26034        }),
26035    )
26036    .await;
26037
26038    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26039    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26040    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26041
26042    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26043    language_registry.add(rust_lang());
26044    let mut fake_servers = language_registry.register_fake_lsp(
26045        "Rust",
26046        FakeLspAdapter {
26047            capabilities: lsp::ServerCapabilities {
26048                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26049                ..lsp::ServerCapabilities::default()
26050            },
26051            name: "rust-analyzer",
26052            ..FakeLspAdapter::default()
26053        },
26054    );
26055    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26056        "Rust",
26057        FakeLspAdapter {
26058            capabilities: lsp::ServerCapabilities {
26059                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26060                ..lsp::ServerCapabilities::default()
26061            },
26062            name: "not-rust-analyzer",
26063            ..FakeLspAdapter::default()
26064        },
26065    );
26066
26067    let editor = workspace
26068        .update(cx, |workspace, window, cx| {
26069            workspace.open_abs_path(
26070                PathBuf::from(path!("/a/first.rs")),
26071                OpenOptions::default(),
26072                window,
26073                cx,
26074            )
26075        })
26076        .unwrap()
26077        .await
26078        .unwrap()
26079        .downcast::<Editor>()
26080        .unwrap();
26081    let fake_language_server = fake_servers.next().await.unwrap();
26082    let fake_language_server_without_capabilities =
26083        fake_servers_without_capabilities.next().await.unwrap();
26084    let requests_made = Arc::new(AtomicUsize::new(0));
26085    let closure_requests_made = Arc::clone(&requests_made);
26086    let mut color_request_handle = fake_language_server
26087        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26088            let requests_made = Arc::clone(&closure_requests_made);
26089            async move {
26090                assert_eq!(
26091                    params.text_document.uri,
26092                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26093                );
26094                requests_made.fetch_add(1, atomic::Ordering::Release);
26095                Ok(vec![
26096                    lsp::ColorInformation {
26097                        range: lsp::Range {
26098                            start: lsp::Position {
26099                                line: 0,
26100                                character: 0,
26101                            },
26102                            end: lsp::Position {
26103                                line: 0,
26104                                character: 1,
26105                            },
26106                        },
26107                        color: lsp::Color {
26108                            red: 0.33,
26109                            green: 0.33,
26110                            blue: 0.33,
26111                            alpha: 0.33,
26112                        },
26113                    },
26114                    lsp::ColorInformation {
26115                        range: lsp::Range {
26116                            start: lsp::Position {
26117                                line: 0,
26118                                character: 0,
26119                            },
26120                            end: lsp::Position {
26121                                line: 0,
26122                                character: 1,
26123                            },
26124                        },
26125                        color: lsp::Color {
26126                            red: 0.33,
26127                            green: 0.33,
26128                            blue: 0.33,
26129                            alpha: 0.33,
26130                        },
26131                    },
26132                ])
26133            }
26134        });
26135
26136    let _handle = fake_language_server_without_capabilities
26137        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26138            panic!("Should not be called");
26139        });
26140    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26141    color_request_handle.next().await.unwrap();
26142    cx.run_until_parked();
26143    assert_eq!(
26144        1,
26145        requests_made.load(atomic::Ordering::Acquire),
26146        "Should query for colors once per editor open"
26147    );
26148    editor.update_in(cx, |editor, _, cx| {
26149        assert_eq!(
26150            vec![expected_color],
26151            extract_color_inlays(editor, cx),
26152            "Should have an initial inlay"
26153        );
26154    });
26155
26156    // opening another file in a split should not influence the LSP query counter
26157    workspace
26158        .update(cx, |workspace, window, cx| {
26159            assert_eq!(
26160                workspace.panes().len(),
26161                1,
26162                "Should have one pane with one editor"
26163            );
26164            workspace.move_item_to_pane_in_direction(
26165                &MoveItemToPaneInDirection {
26166                    direction: SplitDirection::Right,
26167                    focus: false,
26168                    clone: true,
26169                },
26170                window,
26171                cx,
26172            );
26173        })
26174        .unwrap();
26175    cx.run_until_parked();
26176    workspace
26177        .update(cx, |workspace, _, cx| {
26178            let panes = workspace.panes();
26179            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26180            for pane in panes {
26181                let editor = pane
26182                    .read(cx)
26183                    .active_item()
26184                    .and_then(|item| item.downcast::<Editor>())
26185                    .expect("Should have opened an editor in each split");
26186                let editor_file = editor
26187                    .read(cx)
26188                    .buffer()
26189                    .read(cx)
26190                    .as_singleton()
26191                    .expect("test deals with singleton buffers")
26192                    .read(cx)
26193                    .file()
26194                    .expect("test buffese should have a file")
26195                    .path();
26196                assert_eq!(
26197                    editor_file.as_ref(),
26198                    rel_path("first.rs"),
26199                    "Both editors should be opened for the same file"
26200                )
26201            }
26202        })
26203        .unwrap();
26204
26205    cx.executor().advance_clock(Duration::from_millis(500));
26206    let save = editor.update_in(cx, |editor, window, cx| {
26207        editor.move_to_end(&MoveToEnd, window, cx);
26208        editor.handle_input("dirty", window, cx);
26209        editor.save(
26210            SaveOptions {
26211                format: true,
26212                autosave: true,
26213            },
26214            project.clone(),
26215            window,
26216            cx,
26217        )
26218    });
26219    save.await.unwrap();
26220
26221    color_request_handle.next().await.unwrap();
26222    cx.run_until_parked();
26223    assert_eq!(
26224        2,
26225        requests_made.load(atomic::Ordering::Acquire),
26226        "Should query for colors once per save (deduplicated) and once per formatting after save"
26227    );
26228
26229    drop(editor);
26230    let close = workspace
26231        .update(cx, |workspace, window, cx| {
26232            workspace.active_pane().update(cx, |pane, cx| {
26233                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26234            })
26235        })
26236        .unwrap();
26237    close.await.unwrap();
26238    let close = workspace
26239        .update(cx, |workspace, window, cx| {
26240            workspace.active_pane().update(cx, |pane, cx| {
26241                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26242            })
26243        })
26244        .unwrap();
26245    close.await.unwrap();
26246    assert_eq!(
26247        2,
26248        requests_made.load(atomic::Ordering::Acquire),
26249        "After saving and closing all editors, no extra requests should be made"
26250    );
26251    workspace
26252        .update(cx, |workspace, _, cx| {
26253            assert!(
26254                workspace.active_item(cx).is_none(),
26255                "Should close all editors"
26256            )
26257        })
26258        .unwrap();
26259
26260    workspace
26261        .update(cx, |workspace, window, cx| {
26262            workspace.active_pane().update(cx, |pane, cx| {
26263                pane.navigate_backward(&workspace::GoBack, window, cx);
26264            })
26265        })
26266        .unwrap();
26267    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26268    cx.run_until_parked();
26269    let editor = workspace
26270        .update(cx, |workspace, _, cx| {
26271            workspace
26272                .active_item(cx)
26273                .expect("Should have reopened the editor again after navigating back")
26274                .downcast::<Editor>()
26275                .expect("Should be an editor")
26276        })
26277        .unwrap();
26278
26279    assert_eq!(
26280        2,
26281        requests_made.load(atomic::Ordering::Acquire),
26282        "Cache should be reused on buffer close and reopen"
26283    );
26284    editor.update(cx, |editor, cx| {
26285        assert_eq!(
26286            vec![expected_color],
26287            extract_color_inlays(editor, cx),
26288            "Should have an initial inlay"
26289        );
26290    });
26291
26292    drop(color_request_handle);
26293    let closure_requests_made = Arc::clone(&requests_made);
26294    let mut empty_color_request_handle = fake_language_server
26295        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26296            let requests_made = Arc::clone(&closure_requests_made);
26297            async move {
26298                assert_eq!(
26299                    params.text_document.uri,
26300                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26301                );
26302                requests_made.fetch_add(1, atomic::Ordering::Release);
26303                Ok(Vec::new())
26304            }
26305        });
26306    let save = editor.update_in(cx, |editor, window, cx| {
26307        editor.move_to_end(&MoveToEnd, window, cx);
26308        editor.handle_input("dirty_again", window, cx);
26309        editor.save(
26310            SaveOptions {
26311                format: false,
26312                autosave: true,
26313            },
26314            project.clone(),
26315            window,
26316            cx,
26317        )
26318    });
26319    save.await.unwrap();
26320
26321    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26322    empty_color_request_handle.next().await.unwrap();
26323    cx.run_until_parked();
26324    assert_eq!(
26325        3,
26326        requests_made.load(atomic::Ordering::Acquire),
26327        "Should query for colors once per save only, as formatting was not requested"
26328    );
26329    editor.update(cx, |editor, cx| {
26330        assert_eq!(
26331            Vec::<Rgba>::new(),
26332            extract_color_inlays(editor, cx),
26333            "Should clear all colors when the server returns an empty response"
26334        );
26335    });
26336}
26337
26338#[gpui::test]
26339async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26340    init_test(cx, |_| {});
26341    let (editor, cx) = cx.add_window_view(Editor::single_line);
26342    editor.update_in(cx, |editor, window, cx| {
26343        editor.set_text("oops\n\nwow\n", window, cx)
26344    });
26345    cx.run_until_parked();
26346    editor.update(cx, |editor, cx| {
26347        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26348    });
26349    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26350    cx.run_until_parked();
26351    editor.update(cx, |editor, cx| {
26352        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26353    });
26354}
26355
26356#[gpui::test]
26357async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26358    init_test(cx, |_| {});
26359
26360    cx.update(|cx| {
26361        register_project_item::<Editor>(cx);
26362    });
26363
26364    let fs = FakeFs::new(cx.executor());
26365    fs.insert_tree("/root1", json!({})).await;
26366    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26367        .await;
26368
26369    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26370    let (workspace, cx) =
26371        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26372
26373    let worktree_id = project.update(cx, |project, cx| {
26374        project.worktrees(cx).next().unwrap().read(cx).id()
26375    });
26376
26377    let handle = workspace
26378        .update_in(cx, |workspace, window, cx| {
26379            let project_path = (worktree_id, rel_path("one.pdf"));
26380            workspace.open_path(project_path, None, true, window, cx)
26381        })
26382        .await
26383        .unwrap();
26384
26385    assert_eq!(
26386        handle.to_any().entity_type(),
26387        TypeId::of::<InvalidItemView>()
26388    );
26389}
26390
26391#[gpui::test]
26392async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26393    init_test(cx, |_| {});
26394
26395    let language = Arc::new(Language::new(
26396        LanguageConfig::default(),
26397        Some(tree_sitter_rust::LANGUAGE.into()),
26398    ));
26399
26400    // Test hierarchical sibling navigation
26401    let text = r#"
26402        fn outer() {
26403            if condition {
26404                let a = 1;
26405            }
26406            let b = 2;
26407        }
26408
26409        fn another() {
26410            let c = 3;
26411        }
26412    "#;
26413
26414    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26415    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26416    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26417
26418    // Wait for parsing to complete
26419    editor
26420        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26421        .await;
26422
26423    editor.update_in(cx, |editor, window, cx| {
26424        // Start by selecting "let a = 1;" inside the if block
26425        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26426            s.select_display_ranges([
26427                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26428            ]);
26429        });
26430
26431        let initial_selection = editor
26432            .selections
26433            .display_ranges(&editor.display_snapshot(cx));
26434        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26435
26436        // Test select next sibling - should move up levels to find the next sibling
26437        // Since "let a = 1;" has no siblings in the if block, it should move up
26438        // to find "let b = 2;" which is a sibling of the if block
26439        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26440        let next_selection = editor
26441            .selections
26442            .display_ranges(&editor.display_snapshot(cx));
26443
26444        // Should have a selection and it should be different from the initial
26445        assert_eq!(
26446            next_selection.len(),
26447            1,
26448            "Should have one selection after next"
26449        );
26450        assert_ne!(
26451            next_selection[0], initial_selection[0],
26452            "Next sibling selection should be different"
26453        );
26454
26455        // Test hierarchical navigation by going to the end of the current function
26456        // and trying to navigate to the next function
26457        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26458            s.select_display_ranges([
26459                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26460            ]);
26461        });
26462
26463        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26464        let function_next_selection = editor
26465            .selections
26466            .display_ranges(&editor.display_snapshot(cx));
26467
26468        // Should move to the next function
26469        assert_eq!(
26470            function_next_selection.len(),
26471            1,
26472            "Should have one selection after function next"
26473        );
26474
26475        // Test select previous sibling navigation
26476        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26477        let prev_selection = editor
26478            .selections
26479            .display_ranges(&editor.display_snapshot(cx));
26480
26481        // Should have a selection and it should be different
26482        assert_eq!(
26483            prev_selection.len(),
26484            1,
26485            "Should have one selection after prev"
26486        );
26487        assert_ne!(
26488            prev_selection[0], function_next_selection[0],
26489            "Previous sibling selection should be different from next"
26490        );
26491    });
26492}
26493
26494#[gpui::test]
26495async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26496    init_test(cx, |_| {});
26497
26498    let mut cx = EditorTestContext::new(cx).await;
26499    cx.set_state(
26500        "let ˇvariable = 42;
26501let another = variable + 1;
26502let result = variable * 2;",
26503    );
26504
26505    // Set up document highlights manually (simulating LSP response)
26506    cx.update_editor(|editor, _window, cx| {
26507        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26508
26509        // Create highlights for "variable" occurrences
26510        let highlight_ranges = [
26511            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26512            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26513            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26514        ];
26515
26516        let anchor_ranges: Vec<_> = highlight_ranges
26517            .iter()
26518            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26519            .collect();
26520
26521        editor.highlight_background::<DocumentHighlightRead>(
26522            &anchor_ranges,
26523            |theme| theme.colors().editor_document_highlight_read_background,
26524            cx,
26525        );
26526    });
26527
26528    // Go to next highlight - should move to second "variable"
26529    cx.update_editor(|editor, window, cx| {
26530        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26531    });
26532    cx.assert_editor_state(
26533        "let variable = 42;
26534let another = ˇvariable + 1;
26535let result = variable * 2;",
26536    );
26537
26538    // Go to next highlight - should move to third "variable"
26539    cx.update_editor(|editor, window, cx| {
26540        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26541    });
26542    cx.assert_editor_state(
26543        "let variable = 42;
26544let another = variable + 1;
26545let result = ˇvariable * 2;",
26546    );
26547
26548    // Go to next highlight - should stay at third "variable" (no wrap-around)
26549    cx.update_editor(|editor, window, cx| {
26550        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26551    });
26552    cx.assert_editor_state(
26553        "let variable = 42;
26554let another = variable + 1;
26555let result = ˇvariable * 2;",
26556    );
26557
26558    // Now test going backwards from third position
26559    cx.update_editor(|editor, window, cx| {
26560        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26561    });
26562    cx.assert_editor_state(
26563        "let variable = 42;
26564let another = ˇvariable + 1;
26565let result = variable * 2;",
26566    );
26567
26568    // Go to previous highlight - should move to first "variable"
26569    cx.update_editor(|editor, window, cx| {
26570        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26571    });
26572    cx.assert_editor_state(
26573        "let ˇvariable = 42;
26574let another = variable + 1;
26575let result = variable * 2;",
26576    );
26577
26578    // Go to previous highlight - should stay on first "variable"
26579    cx.update_editor(|editor, window, cx| {
26580        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26581    });
26582    cx.assert_editor_state(
26583        "let ˇvariable = 42;
26584let another = variable + 1;
26585let result = variable * 2;",
26586    );
26587}
26588
26589#[gpui::test]
26590async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26591    cx: &mut gpui::TestAppContext,
26592) {
26593    init_test(cx, |_| {});
26594
26595    let url = "https://zed.dev";
26596
26597    let markdown_language = Arc::new(Language::new(
26598        LanguageConfig {
26599            name: "Markdown".into(),
26600            ..LanguageConfig::default()
26601        },
26602        None,
26603    ));
26604
26605    let mut cx = EditorTestContext::new(cx).await;
26606    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26607    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26608
26609    cx.update_editor(|editor, window, cx| {
26610        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26611        editor.paste(&Paste, window, cx);
26612    });
26613
26614    cx.assert_editor_state(&format!(
26615        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26616    ));
26617}
26618
26619#[gpui::test]
26620async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26621    cx: &mut gpui::TestAppContext,
26622) {
26623    init_test(cx, |_| {});
26624
26625    let url = "https://zed.dev";
26626
26627    let markdown_language = Arc::new(Language::new(
26628        LanguageConfig {
26629            name: "Markdown".into(),
26630            ..LanguageConfig::default()
26631        },
26632        None,
26633    ));
26634
26635    let mut cx = EditorTestContext::new(cx).await;
26636    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26637    cx.set_state(&format!(
26638        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26639    ));
26640
26641    cx.update_editor(|editor, window, cx| {
26642        editor.copy(&Copy, window, cx);
26643    });
26644
26645    cx.set_state(&format!(
26646        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26647    ));
26648
26649    cx.update_editor(|editor, window, cx| {
26650        editor.paste(&Paste, window, cx);
26651    });
26652
26653    cx.assert_editor_state(&format!(
26654        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26655    ));
26656}
26657
26658#[gpui::test]
26659async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26660    cx: &mut gpui::TestAppContext,
26661) {
26662    init_test(cx, |_| {});
26663
26664    let url = "https://zed.dev";
26665
26666    let markdown_language = Arc::new(Language::new(
26667        LanguageConfig {
26668            name: "Markdown".into(),
26669            ..LanguageConfig::default()
26670        },
26671        None,
26672    ));
26673
26674    let mut cx = EditorTestContext::new(cx).await;
26675    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26676    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26677
26678    cx.update_editor(|editor, window, cx| {
26679        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26680        editor.paste(&Paste, window, cx);
26681    });
26682
26683    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26684}
26685
26686#[gpui::test]
26687async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26688    cx: &mut gpui::TestAppContext,
26689) {
26690    init_test(cx, |_| {});
26691
26692    let text = "Awesome";
26693
26694    let markdown_language = Arc::new(Language::new(
26695        LanguageConfig {
26696            name: "Markdown".into(),
26697            ..LanguageConfig::default()
26698        },
26699        None,
26700    ));
26701
26702    let mut cx = EditorTestContext::new(cx).await;
26703    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26704    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26705
26706    cx.update_editor(|editor, window, cx| {
26707        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26708        editor.paste(&Paste, window, cx);
26709    });
26710
26711    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26712}
26713
26714#[gpui::test]
26715async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26716    cx: &mut gpui::TestAppContext,
26717) {
26718    init_test(cx, |_| {});
26719
26720    let url = "https://zed.dev";
26721
26722    let markdown_language = Arc::new(Language::new(
26723        LanguageConfig {
26724            name: "Rust".into(),
26725            ..LanguageConfig::default()
26726        },
26727        None,
26728    ));
26729
26730    let mut cx = EditorTestContext::new(cx).await;
26731    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26732    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26733
26734    cx.update_editor(|editor, window, cx| {
26735        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26736        editor.paste(&Paste, window, cx);
26737    });
26738
26739    cx.assert_editor_state(&format!(
26740        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26741    ));
26742}
26743
26744#[gpui::test]
26745async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26746    cx: &mut TestAppContext,
26747) {
26748    init_test(cx, |_| {});
26749
26750    let url = "https://zed.dev";
26751
26752    let markdown_language = Arc::new(Language::new(
26753        LanguageConfig {
26754            name: "Markdown".into(),
26755            ..LanguageConfig::default()
26756        },
26757        None,
26758    ));
26759
26760    let (editor, cx) = cx.add_window_view(|window, cx| {
26761        let multi_buffer = MultiBuffer::build_multi(
26762            [
26763                ("this will embed -> link", vec![Point::row_range(0..1)]),
26764                ("this will replace -> link", vec![Point::row_range(0..1)]),
26765            ],
26766            cx,
26767        );
26768        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26769        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26770            s.select_ranges(vec![
26771                Point::new(0, 19)..Point::new(0, 23),
26772                Point::new(1, 21)..Point::new(1, 25),
26773            ])
26774        });
26775        let first_buffer_id = multi_buffer
26776            .read(cx)
26777            .excerpt_buffer_ids()
26778            .into_iter()
26779            .next()
26780            .unwrap();
26781        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26782        first_buffer.update(cx, |buffer, cx| {
26783            buffer.set_language(Some(markdown_language.clone()), cx);
26784        });
26785
26786        editor
26787    });
26788    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26789
26790    cx.update_editor(|editor, window, cx| {
26791        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26792        editor.paste(&Paste, window, cx);
26793    });
26794
26795    cx.assert_editor_state(&format!(
26796        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26797    ));
26798}
26799
26800#[gpui::test]
26801async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26802    init_test(cx, |_| {});
26803
26804    let fs = FakeFs::new(cx.executor());
26805    fs.insert_tree(
26806        path!("/project"),
26807        json!({
26808            "first.rs": "# First Document\nSome content here.",
26809            "second.rs": "Plain text content for second file.",
26810        }),
26811    )
26812    .await;
26813
26814    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26815    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26816    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26817
26818    let language = rust_lang();
26819    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26820    language_registry.add(language.clone());
26821    let mut fake_servers = language_registry.register_fake_lsp(
26822        "Rust",
26823        FakeLspAdapter {
26824            ..FakeLspAdapter::default()
26825        },
26826    );
26827
26828    let buffer1 = project
26829        .update(cx, |project, cx| {
26830            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26831        })
26832        .await
26833        .unwrap();
26834    let buffer2 = project
26835        .update(cx, |project, cx| {
26836            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26837        })
26838        .await
26839        .unwrap();
26840
26841    let multi_buffer = cx.new(|cx| {
26842        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26843        multi_buffer.set_excerpts_for_path(
26844            PathKey::for_buffer(&buffer1, cx),
26845            buffer1.clone(),
26846            [Point::zero()..buffer1.read(cx).max_point()],
26847            3,
26848            cx,
26849        );
26850        multi_buffer.set_excerpts_for_path(
26851            PathKey::for_buffer(&buffer2, cx),
26852            buffer2.clone(),
26853            [Point::zero()..buffer1.read(cx).max_point()],
26854            3,
26855            cx,
26856        );
26857        multi_buffer
26858    });
26859
26860    let (editor, cx) = cx.add_window_view(|window, cx| {
26861        Editor::new(
26862            EditorMode::full(),
26863            multi_buffer,
26864            Some(project.clone()),
26865            window,
26866            cx,
26867        )
26868    });
26869
26870    let fake_language_server = fake_servers.next().await.unwrap();
26871
26872    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26873
26874    let save = editor.update_in(cx, |editor, window, cx| {
26875        assert!(editor.is_dirty(cx));
26876
26877        editor.save(
26878            SaveOptions {
26879                format: true,
26880                autosave: true,
26881            },
26882            project,
26883            window,
26884            cx,
26885        )
26886    });
26887    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26888    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26889    let mut done_edit_rx = Some(done_edit_rx);
26890    let mut start_edit_tx = Some(start_edit_tx);
26891
26892    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26893        start_edit_tx.take().unwrap().send(()).unwrap();
26894        let done_edit_rx = done_edit_rx.take().unwrap();
26895        async move {
26896            done_edit_rx.await.unwrap();
26897            Ok(None)
26898        }
26899    });
26900
26901    start_edit_rx.await.unwrap();
26902    buffer2
26903        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26904        .unwrap();
26905
26906    done_edit_tx.send(()).unwrap();
26907
26908    save.await.unwrap();
26909    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26910}
26911
26912#[track_caller]
26913fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26914    editor
26915        .all_inlays(cx)
26916        .into_iter()
26917        .filter_map(|inlay| inlay.get_color())
26918        .map(Rgba::from)
26919        .collect()
26920}
26921
26922#[gpui::test]
26923fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26924    init_test(cx, |_| {});
26925
26926    let editor = cx.add_window(|window, cx| {
26927        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26928        build_editor(buffer, window, cx)
26929    });
26930
26931    editor
26932        .update(cx, |editor, window, cx| {
26933            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26934                s.select_display_ranges([
26935                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26936                ])
26937            });
26938
26939            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26940
26941            assert_eq!(
26942                editor.display_text(cx),
26943                "line1\nline2\nline2",
26944                "Duplicating last line upward should create duplicate above, not on same line"
26945            );
26946
26947            assert_eq!(
26948                editor
26949                    .selections
26950                    .display_ranges(&editor.display_snapshot(cx)),
26951                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
26952                "Selection should move to the duplicated line"
26953            );
26954        })
26955        .unwrap();
26956}
26957
26958#[gpui::test]
26959async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26960    init_test(cx, |_| {});
26961
26962    let mut cx = EditorTestContext::new(cx).await;
26963
26964    cx.set_state("line1\nline2ˇ");
26965
26966    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26967
26968    let clipboard_text = cx
26969        .read_from_clipboard()
26970        .and_then(|item| item.text().as_deref().map(str::to_string));
26971
26972    assert_eq!(
26973        clipboard_text,
26974        Some("line2\n".to_string()),
26975        "Copying a line without trailing newline should include a newline"
26976    );
26977
26978    cx.set_state("line1\nˇ");
26979
26980    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
26981
26982    cx.assert_editor_state("line1\nline2\nˇ");
26983}
26984
26985#[gpui::test]
26986async fn test_end_of_editor_context(cx: &mut TestAppContext) {
26987    init_test(cx, |_| {});
26988
26989    let mut cx = EditorTestContext::new(cx).await;
26990
26991    cx.set_state("line1\nline2ˇ");
26992    cx.update_editor(|e, window, cx| {
26993        e.set_mode(EditorMode::SingleLine);
26994        assert!(e.key_context(window, cx).contains("end_of_input"));
26995    });
26996    cx.set_state("ˇline1\nline2");
26997    cx.update_editor(|e, window, cx| {
26998        assert!(!e.key_context(window, cx).contains("end_of_input"));
26999    });
27000    cx.set_state("line1ˇ\nline2");
27001    cx.update_editor(|e, window, cx| {
27002        assert!(!e.key_context(window, cx).contains("end_of_input"));
27003    });
27004}
27005
27006#[gpui::test]
27007async fn test_next_prev_reference(cx: &mut TestAppContext) {
27008    const CYCLE_POSITIONS: &[&'static str] = &[
27009        indoc! {"
27010            fn foo() {
27011                let ˇabc = 123;
27012                let x = abc + 1;
27013                let y = abc + 2;
27014                let z = abc + 2;
27015            }
27016        "},
27017        indoc! {"
27018            fn foo() {
27019                let abc = 123;
27020                let x = ˇabc + 1;
27021                let y = abc + 2;
27022                let z = abc + 2;
27023            }
27024        "},
27025        indoc! {"
27026            fn foo() {
27027                let abc = 123;
27028                let x = abc + 1;
27029                let y = ˇabc + 2;
27030                let z = abc + 2;
27031            }
27032        "},
27033        indoc! {"
27034            fn foo() {
27035                let abc = 123;
27036                let x = abc + 1;
27037                let y = abc + 2;
27038                let z = ˇabc + 2;
27039            }
27040        "},
27041    ];
27042
27043    init_test(cx, |_| {});
27044
27045    let mut cx = EditorLspTestContext::new_rust(
27046        lsp::ServerCapabilities {
27047            references_provider: Some(lsp::OneOf::Left(true)),
27048            ..Default::default()
27049        },
27050        cx,
27051    )
27052    .await;
27053
27054    // importantly, the cursor is in the middle
27055    cx.set_state(indoc! {"
27056        fn foo() {
27057            let aˇbc = 123;
27058            let x = abc + 1;
27059            let y = abc + 2;
27060            let z = abc + 2;
27061        }
27062    "});
27063
27064    let reference_ranges = [
27065        lsp::Position::new(1, 8),
27066        lsp::Position::new(2, 12),
27067        lsp::Position::new(3, 12),
27068        lsp::Position::new(4, 12),
27069    ]
27070    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
27071
27072    cx.lsp
27073        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
27074            Ok(Some(
27075                reference_ranges
27076                    .map(|range| lsp::Location {
27077                        uri: params.text_document_position.text_document.uri.clone(),
27078                        range,
27079                    })
27080                    .to_vec(),
27081            ))
27082        });
27083
27084    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
27085        cx.update_editor(|editor, window, cx| {
27086            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
27087        })
27088        .unwrap()
27089        .await
27090        .unwrap()
27091    };
27092
27093    _move(Direction::Next, 1, &mut cx).await;
27094    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27095
27096    _move(Direction::Next, 1, &mut cx).await;
27097    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27098
27099    _move(Direction::Next, 1, &mut cx).await;
27100    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27101
27102    // loops back to the start
27103    _move(Direction::Next, 1, &mut cx).await;
27104    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27105
27106    // loops back to the end
27107    _move(Direction::Prev, 1, &mut cx).await;
27108    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27109
27110    _move(Direction::Prev, 1, &mut cx).await;
27111    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27112
27113    _move(Direction::Prev, 1, &mut cx).await;
27114    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27115
27116    _move(Direction::Prev, 1, &mut cx).await;
27117    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27118
27119    _move(Direction::Next, 3, &mut cx).await;
27120    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27121
27122    _move(Direction::Prev, 2, &mut cx).await;
27123    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27124}