editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    element::StickyHeader,
    7    linked_editing_ranges::LinkedEditingRanges,
    8    scroll::scroll_amount::ScrollAmount,
    9    test::{
   10        assert_text_with_selections, build_editor,
   11        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   12        editor_test_context::EditorTestContext,
   13        select_ranges,
   14    },
   15};
   16use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   17use collections::HashMap;
   18use futures::{StreamExt, channel::oneshot};
   19use gpui::{
   20    BackgroundExecutor, DismissEvent, Rgba, TestAppContext, UpdateGlobal, VisualTestContext,
   21    WindowBounds, WindowOptions, div,
   22};
   23use indoc::indoc;
   24use language::{
   25    BracketPairConfig,
   26    Capability::ReadWrite,
   27    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   28    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   29    language_settings::{
   30        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use languages::markdown_lang;
   36use languages::rust_lang;
   37use lsp::CompletionParams;
   38use multi_buffer::{IndentGuide, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey};
   39use parking_lot::Mutex;
   40use pretty_assertions::{assert_eq, assert_ne};
   41use project::{
   42    FakeFs,
   43    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   44    project_settings::LspSettings,
   45};
   46use serde_json::{self, json};
   47use settings::{
   48    AllLanguageSettingsContent, EditorSettingsContent, IndentGuideBackgroundColoring,
   49    IndentGuideColoring, ProjectSettingsContent, SearchSettingsContent,
   50};
   51use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   52use std::{
   53    iter,
   54    sync::atomic::{self, AtomicUsize},
   55};
   56use test::build_editor_with_project;
   57use text::ToPoint as _;
   58use unindent::Unindent;
   59use util::{
   60    assert_set_eq, path,
   61    rel_path::rel_path,
   62    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   63    uri,
   64};
   65use workspace::{
   66    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   67    OpenOptions, ViewId,
   68    invalid_item_view::InvalidItemView,
   69    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   70    register_project_item,
   71};
   72
   73fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   74    editor
   75        .selections
   76        .display_ranges(&editor.display_snapshot(cx))
   77}
   78
   79#[gpui::test]
   80fn test_edit_events(cx: &mut TestAppContext) {
   81    init_test(cx, |_| {});
   82
   83    let buffer = cx.new(|cx| {
   84        let mut buffer = language::Buffer::local("123456", cx);
   85        buffer.set_group_interval(Duration::from_secs(1));
   86        buffer
   87    });
   88
   89    let events = Rc::new(RefCell::new(Vec::new()));
   90    let editor1 = cx.add_window({
   91        let events = events.clone();
   92        |window, cx| {
   93            let entity = cx.entity();
   94            cx.subscribe_in(
   95                &entity,
   96                window,
   97                move |_, _, event: &EditorEvent, _, _| match event {
   98                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   99                    EditorEvent::BufferEdited => {
  100                        events.borrow_mut().push(("editor1", "buffer edited"))
  101                    }
  102                    _ => {}
  103                },
  104            )
  105            .detach();
  106            Editor::for_buffer(buffer.clone(), None, window, cx)
  107        }
  108    });
  109
  110    let editor2 = cx.add_window({
  111        let events = events.clone();
  112        |window, cx| {
  113            cx.subscribe_in(
  114                &cx.entity(),
  115                window,
  116                move |_, _, event: &EditorEvent, _, _| match event {
  117                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  118                    EditorEvent::BufferEdited => {
  119                        events.borrow_mut().push(("editor2", "buffer edited"))
  120                    }
  121                    _ => {}
  122                },
  123            )
  124            .detach();
  125            Editor::for_buffer(buffer.clone(), None, window, cx)
  126        }
  127    });
  128
  129    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  130
  131    // Mutating editor 1 will emit an `Edited` event only for that editor.
  132    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  133    assert_eq!(
  134        mem::take(&mut *events.borrow_mut()),
  135        [
  136            ("editor1", "edited"),
  137            ("editor1", "buffer edited"),
  138            ("editor2", "buffer edited"),
  139        ]
  140    );
  141
  142    // Mutating editor 2 will emit an `Edited` event only for that editor.
  143    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  144    assert_eq!(
  145        mem::take(&mut *events.borrow_mut()),
  146        [
  147            ("editor2", "edited"),
  148            ("editor1", "buffer edited"),
  149            ("editor2", "buffer edited"),
  150        ]
  151    );
  152
  153    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  154    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  155    assert_eq!(
  156        mem::take(&mut *events.borrow_mut()),
  157        [
  158            ("editor1", "edited"),
  159            ("editor1", "buffer edited"),
  160            ("editor2", "buffer edited"),
  161        ]
  162    );
  163
  164    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  165    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  166    assert_eq!(
  167        mem::take(&mut *events.borrow_mut()),
  168        [
  169            ("editor1", "edited"),
  170            ("editor1", "buffer edited"),
  171            ("editor2", "buffer edited"),
  172        ]
  173    );
  174
  175    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  176    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  177    assert_eq!(
  178        mem::take(&mut *events.borrow_mut()),
  179        [
  180            ("editor2", "edited"),
  181            ("editor1", "buffer edited"),
  182            ("editor2", "buffer edited"),
  183        ]
  184    );
  185
  186    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  187    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  188    assert_eq!(
  189        mem::take(&mut *events.borrow_mut()),
  190        [
  191            ("editor2", "edited"),
  192            ("editor1", "buffer edited"),
  193            ("editor2", "buffer edited"),
  194        ]
  195    );
  196
  197    // No event is emitted when the mutation is a no-op.
  198    _ = editor2.update(cx, |editor, window, cx| {
  199        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  200            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  201        });
  202
  203        editor.backspace(&Backspace, window, cx);
  204    });
  205    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  206}
  207
  208#[gpui::test]
  209fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  210    init_test(cx, |_| {});
  211
  212    let mut now = Instant::now();
  213    let group_interval = Duration::from_millis(1);
  214    let buffer = cx.new(|cx| {
  215        let mut buf = language::Buffer::local("123456", cx);
  216        buf.set_group_interval(group_interval);
  217        buf
  218    });
  219    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  220    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  221
  222    _ = editor.update(cx, |editor, window, cx| {
  223        editor.start_transaction_at(now, window, cx);
  224        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  225            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  226        });
  227
  228        editor.insert("cd", window, cx);
  229        editor.end_transaction_at(now, cx);
  230        assert_eq!(editor.text(cx), "12cd56");
  231        assert_eq!(
  232            editor.selections.ranges(&editor.display_snapshot(cx)),
  233            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  234        );
  235
  236        editor.start_transaction_at(now, window, cx);
  237        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  238            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  239        });
  240        editor.insert("e", window, cx);
  241        editor.end_transaction_at(now, cx);
  242        assert_eq!(editor.text(cx), "12cde6");
  243        assert_eq!(
  244            editor.selections.ranges(&editor.display_snapshot(cx)),
  245            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  246        );
  247
  248        now += group_interval + Duration::from_millis(1);
  249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  250            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  251        });
  252
  253        // Simulate an edit in another editor
  254        buffer.update(cx, |buffer, cx| {
  255            buffer.start_transaction_at(now, cx);
  256            buffer.edit(
  257                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  258                None,
  259                cx,
  260            );
  261            buffer.edit(
  262                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  263                None,
  264                cx,
  265            );
  266            buffer.end_transaction_at(now, cx);
  267        });
  268
  269        assert_eq!(editor.text(cx), "ab2cde6");
  270        assert_eq!(
  271            editor.selections.ranges(&editor.display_snapshot(cx)),
  272            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  273        );
  274
  275        // Last transaction happened past the group interval in a different editor.
  276        // Undo it individually and don't restore selections.
  277        editor.undo(&Undo, window, cx);
  278        assert_eq!(editor.text(cx), "12cde6");
  279        assert_eq!(
  280            editor.selections.ranges(&editor.display_snapshot(cx)),
  281            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  282        );
  283
  284        // First two transactions happened within the group interval in this editor.
  285        // Undo them together and restore selections.
  286        editor.undo(&Undo, window, cx);
  287        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  288        assert_eq!(editor.text(cx), "123456");
  289        assert_eq!(
  290            editor.selections.ranges(&editor.display_snapshot(cx)),
  291            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  292        );
  293
  294        // Redo the first two transactions together.
  295        editor.redo(&Redo, window, cx);
  296        assert_eq!(editor.text(cx), "12cde6");
  297        assert_eq!(
  298            editor.selections.ranges(&editor.display_snapshot(cx)),
  299            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  300        );
  301
  302        // Redo the last transaction on its own.
  303        editor.redo(&Redo, window, cx);
  304        assert_eq!(editor.text(cx), "ab2cde6");
  305        assert_eq!(
  306            editor.selections.ranges(&editor.display_snapshot(cx)),
  307            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  308        );
  309
  310        // Test empty transactions.
  311        editor.start_transaction_at(now, window, cx);
  312        editor.end_transaction_at(now, cx);
  313        editor.undo(&Undo, window, cx);
  314        assert_eq!(editor.text(cx), "12cde6");
  315    });
  316}
  317
  318#[gpui::test]
  319fn test_ime_composition(cx: &mut TestAppContext) {
  320    init_test(cx, |_| {});
  321
  322    let buffer = cx.new(|cx| {
  323        let mut buffer = language::Buffer::local("abcde", cx);
  324        // Ensure automatic grouping doesn't occur.
  325        buffer.set_group_interval(Duration::ZERO);
  326        buffer
  327    });
  328
  329    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  330    cx.add_window(|window, cx| {
  331        let mut editor = build_editor(buffer.clone(), window, cx);
  332
  333        // Start a new IME composition.
  334        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  335        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  336        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  337        assert_eq!(editor.text(cx), "äbcde");
  338        assert_eq!(
  339            editor.marked_text_ranges(cx),
  340            Some(vec![
  341                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  342            ])
  343        );
  344
  345        // Finalize IME composition.
  346        editor.replace_text_in_range(None, "ā", window, cx);
  347        assert_eq!(editor.text(cx), "ābcde");
  348        assert_eq!(editor.marked_text_ranges(cx), None);
  349
  350        // IME composition edits are grouped and are undone/redone at once.
  351        editor.undo(&Default::default(), window, cx);
  352        assert_eq!(editor.text(cx), "abcde");
  353        assert_eq!(editor.marked_text_ranges(cx), None);
  354        editor.redo(&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.
  359        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  360        assert_eq!(
  361            editor.marked_text_ranges(cx),
  362            Some(vec![
  363                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  364            ])
  365        );
  366
  367        // Undoing during an IME composition cancels it.
  368        editor.undo(&Default::default(), window, cx);
  369        assert_eq!(editor.text(cx), "ābcde");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  373        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  374        assert_eq!(editor.text(cx), "ābcdè");
  375        assert_eq!(
  376            editor.marked_text_ranges(cx),
  377            Some(vec![
  378                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  379            ])
  380        );
  381
  382        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  383        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  384        assert_eq!(editor.text(cx), "ābcdę");
  385        assert_eq!(editor.marked_text_ranges(cx), None);
  386
  387        // Start a new IME composition with multiple cursors.
  388        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  389            s.select_ranges([
  390                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  391                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  392                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  393            ])
  394        });
  395        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  396        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  397        assert_eq!(
  398            editor.marked_text_ranges(cx),
  399            Some(vec![
  400                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  401                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  402                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  403            ])
  404        );
  405
  406        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  407        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  408        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  409        assert_eq!(
  410            editor.marked_text_ranges(cx),
  411            Some(vec![
  412                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  413                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  414                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  415            ])
  416        );
  417
  418        // Finalize IME composition with multiple cursors.
  419        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  420        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  421        assert_eq!(editor.marked_text_ranges(cx), None);
  422
  423        editor
  424    });
  425}
  426
  427#[gpui::test]
  428fn test_selection_with_mouse(cx: &mut TestAppContext) {
  429    init_test(cx, |_| {});
  430
  431    let editor = cx.add_window(|window, cx| {
  432        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  433        build_editor(buffer, window, cx)
  434    });
  435
  436    _ = editor.update(cx, |editor, window, cx| {
  437        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  438    });
  439    assert_eq!(
  440        editor
  441            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  442            .unwrap(),
  443        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  444    );
  445
  446    _ = editor.update(cx, |editor, window, cx| {
  447        editor.update_selection(
  448            DisplayPoint::new(DisplayRow(3), 3),
  449            0,
  450            gpui::Point::<f32>::default(),
  451            window,
  452            cx,
  453        );
  454    });
  455
  456    assert_eq!(
  457        editor
  458            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  459            .unwrap(),
  460        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  461    );
  462
  463    _ = editor.update(cx, |editor, window, cx| {
  464        editor.update_selection(
  465            DisplayPoint::new(DisplayRow(1), 1),
  466            0,
  467            gpui::Point::<f32>::default(),
  468            window,
  469            cx,
  470        );
  471    });
  472
  473    assert_eq!(
  474        editor
  475            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  476            .unwrap(),
  477        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  478    );
  479
  480    _ = editor.update(cx, |editor, window, cx| {
  481        editor.end_selection(window, cx);
  482        editor.update_selection(
  483            DisplayPoint::new(DisplayRow(3), 3),
  484            0,
  485            gpui::Point::<f32>::default(),
  486            window,
  487            cx,
  488        );
  489    });
  490
  491    assert_eq!(
  492        editor
  493            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  494            .unwrap(),
  495        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  496    );
  497
  498    _ = editor.update(cx, |editor, window, cx| {
  499        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  500        editor.update_selection(
  501            DisplayPoint::new(DisplayRow(0), 0),
  502            0,
  503            gpui::Point::<f32>::default(),
  504            window,
  505            cx,
  506        );
  507    });
  508
  509    assert_eq!(
  510        editor
  511            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  512            .unwrap(),
  513        [
  514            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  515            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  516        ]
  517    );
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  542    });
  543
  544    _ = editor.update(cx, |editor, window, cx| {
  545        editor.end_selection(window, cx);
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  550    });
  551
  552    _ = editor.update(cx, |editor, window, cx| {
  553        editor.end_selection(window, cx);
  554    });
  555
  556    assert_eq!(
  557        editor
  558            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  559            .unwrap(),
  560        [
  561            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  562            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  563        ]
  564    );
  565
  566    _ = editor.update(cx, |editor, window, cx| {
  567        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  568    });
  569
  570    _ = editor.update(cx, |editor, window, cx| {
  571        editor.end_selection(window, cx);
  572    });
  573
  574    assert_eq!(
  575        editor
  576            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  577            .unwrap(),
  578        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  579    );
  580}
  581
  582#[gpui::test]
  583fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  584    init_test(cx, |_| {});
  585
  586    let editor = cx.add_window(|window, cx| {
  587        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  588        build_editor(buffer, window, cx)
  589    });
  590
  591    _ = editor.update(cx, |editor, window, cx| {
  592        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  593        assert_eq!(
  594            display_ranges(editor, cx),
  595            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  596        );
  597    });
  598
  599    _ = editor.update(cx, |editor, window, cx| {
  600        editor.update_selection(
  601            DisplayPoint::new(DisplayRow(3), 3),
  602            0,
  603            gpui::Point::<f32>::default(),
  604            window,
  605            cx,
  606        );
  607        assert_eq!(
  608            display_ranges(editor, cx),
  609            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  610        );
  611    });
  612
  613    _ = editor.update(cx, |editor, window, cx| {
  614        editor.cancel(&Cancel, window, cx);
  615        editor.update_selection(
  616            DisplayPoint::new(DisplayRow(1), 1),
  617            0,
  618            gpui::Point::<f32>::default(),
  619            window,
  620            cx,
  621        );
  622        assert_eq!(
  623            display_ranges(editor, cx),
  624            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  625        );
  626    });
  627}
  628
  629#[gpui::test]
  630fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  631    init_test(cx, |_| {});
  632
  633    let editor = cx.add_window(|window, cx| {
  634        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  635        build_editor(buffer, window, cx)
  636    });
  637
  638    _ = editor.update(cx, |editor, window, cx| {
  639        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  640        assert_eq!(
  641            display_ranges(editor, cx),
  642            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  643        );
  644
  645        editor.move_down(&Default::default(), window, cx);
  646        assert_eq!(
  647            display_ranges(editor, cx),
  648            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  649        );
  650
  651        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  652        assert_eq!(
  653            display_ranges(editor, cx),
  654            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  655        );
  656
  657        editor.move_up(&Default::default(), window, cx);
  658        assert_eq!(
  659            display_ranges(editor, cx),
  660            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  661        );
  662    });
  663}
  664
  665#[gpui::test]
  666fn test_extending_selection(cx: &mut TestAppContext) {
  667    init_test(cx, |_| {});
  668
  669    let editor = cx.add_window(|window, cx| {
  670        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  671        build_editor(buffer, window, cx)
  672    });
  673
  674    _ = editor.update(cx, |editor, window, cx| {
  675        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  676        editor.end_selection(window, cx);
  677        assert_eq!(
  678            display_ranges(editor, cx),
  679            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  680        );
  681
  682        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  683        editor.end_selection(window, cx);
  684        assert_eq!(
  685            display_ranges(editor, cx),
  686            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  687        );
  688
  689        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  690        editor.end_selection(window, cx);
  691        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  692        assert_eq!(
  693            display_ranges(editor, cx),
  694            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  695        );
  696
  697        editor.update_selection(
  698            DisplayPoint::new(DisplayRow(0), 1),
  699            0,
  700            gpui::Point::<f32>::default(),
  701            window,
  702            cx,
  703        );
  704        editor.end_selection(window, cx);
  705        assert_eq!(
  706            display_ranges(editor, cx),
  707            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  708        );
  709
  710        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  711        editor.end_selection(window, cx);
  712        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  713        editor.end_selection(window, cx);
  714        assert_eq!(
  715            display_ranges(editor, cx),
  716            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  717        );
  718
  719        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  720        assert_eq!(
  721            display_ranges(editor, cx),
  722            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  723        );
  724
  725        editor.update_selection(
  726            DisplayPoint::new(DisplayRow(0), 6),
  727            0,
  728            gpui::Point::<f32>::default(),
  729            window,
  730            cx,
  731        );
  732        assert_eq!(
  733            display_ranges(editor, cx),
  734            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  735        );
  736
  737        editor.update_selection(
  738            DisplayPoint::new(DisplayRow(0), 1),
  739            0,
  740            gpui::Point::<f32>::default(),
  741            window,
  742            cx,
  743        );
  744        editor.end_selection(window, cx);
  745        assert_eq!(
  746            display_ranges(editor, cx),
  747            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  748        );
  749    });
  750}
  751
  752#[gpui::test]
  753fn test_clone(cx: &mut TestAppContext) {
  754    init_test(cx, |_| {});
  755
  756    let (text, selection_ranges) = marked_text_ranges(
  757        indoc! {"
  758            one
  759            two
  760            threeˇ
  761            four
  762            fiveˇ
  763        "},
  764        true,
  765    );
  766
  767    let editor = cx.add_window(|window, cx| {
  768        let buffer = MultiBuffer::build_simple(&text, cx);
  769        build_editor(buffer, window, cx)
  770    });
  771
  772    _ = editor.update(cx, |editor, window, cx| {
  773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  774            s.select_ranges(
  775                selection_ranges
  776                    .iter()
  777                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  778            )
  779        });
  780        editor.fold_creases(
  781            vec![
  782                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  783                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  784            ],
  785            true,
  786            window,
  787            cx,
  788        );
  789    });
  790
  791    let cloned_editor = editor
  792        .update(cx, |editor, _, cx| {
  793            cx.open_window(Default::default(), |window, cx| {
  794                cx.new(|cx| editor.clone(window, cx))
  795            })
  796        })
  797        .unwrap()
  798        .unwrap();
  799
  800    let snapshot = editor
  801        .update(cx, |e, window, cx| e.snapshot(window, cx))
  802        .unwrap();
  803    let cloned_snapshot = cloned_editor
  804        .update(cx, |e, window, cx| e.snapshot(window, cx))
  805        .unwrap();
  806
  807    assert_eq!(
  808        cloned_editor
  809            .update(cx, |e, _, cx| e.display_text(cx))
  810            .unwrap(),
  811        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  812    );
  813    assert_eq!(
  814        cloned_snapshot
  815            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  816            .collect::<Vec<_>>(),
  817        snapshot
  818            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  819            .collect::<Vec<_>>(),
  820    );
  821    assert_set_eq!(
  822        cloned_editor
  823            .update(cx, |editor, _, cx| editor
  824                .selections
  825                .ranges::<Point>(&editor.display_snapshot(cx)))
  826            .unwrap(),
  827        editor
  828            .update(cx, |editor, _, cx| editor
  829                .selections
  830                .ranges(&editor.display_snapshot(cx)))
  831            .unwrap()
  832    );
  833    assert_set_eq!(
  834        cloned_editor
  835            .update(cx, |e, _window, cx| e
  836                .selections
  837                .display_ranges(&e.display_snapshot(cx)))
  838            .unwrap(),
  839        editor
  840            .update(cx, |e, _, cx| e
  841                .selections
  842                .display_ranges(&e.display_snapshot(cx)))
  843            .unwrap()
  844    );
  845}
  846
  847#[gpui::test]
  848async fn test_navigation_history(cx: &mut TestAppContext) {
  849    init_test(cx, |_| {});
  850
  851    use workspace::item::Item;
  852
  853    let fs = FakeFs::new(cx.executor());
  854    let project = Project::test(fs, [], cx).await;
  855    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  856    let pane = workspace
  857        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  858        .unwrap();
  859
  860    _ = workspace.update(cx, |_v, window, cx| {
  861        cx.new(|cx| {
  862            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  863            let mut editor = build_editor(buffer, window, cx);
  864            let handle = cx.entity();
  865            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  866
  867            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  868                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  869            }
  870
  871            // Move the cursor a small distance.
  872            // Nothing is added to the navigation history.
  873            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  874                s.select_display_ranges([
  875                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  876                ])
  877            });
  878            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  879                s.select_display_ranges([
  880                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  881                ])
  882            });
  883            assert!(pop_history(&mut editor, cx).is_none());
  884
  885            // Move the cursor a large distance.
  886            // The history can jump back to the previous position.
  887            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  888                s.select_display_ranges([
  889                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  890                ])
  891            });
  892            let nav_entry = pop_history(&mut editor, cx).unwrap();
  893            editor.navigate(nav_entry.data.unwrap(), window, cx);
  894            assert_eq!(nav_entry.item.id(), cx.entity_id());
  895            assert_eq!(
  896                editor
  897                    .selections
  898                    .display_ranges(&editor.display_snapshot(cx)),
  899                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  900            );
  901            assert!(pop_history(&mut editor, cx).is_none());
  902
  903            // Move the cursor a small distance via the mouse.
  904            // Nothing is added to the navigation history.
  905            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  906            editor.end_selection(window, cx);
  907            assert_eq!(
  908                editor
  909                    .selections
  910                    .display_ranges(&editor.display_snapshot(cx)),
  911                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  912            );
  913            assert!(pop_history(&mut editor, cx).is_none());
  914
  915            // Move the cursor a large distance via the mouse.
  916            // The history can jump back to the previous position.
  917            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  918            editor.end_selection(window, cx);
  919            assert_eq!(
  920                editor
  921                    .selections
  922                    .display_ranges(&editor.display_snapshot(cx)),
  923                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  924            );
  925            let nav_entry = pop_history(&mut editor, cx).unwrap();
  926            editor.navigate(nav_entry.data.unwrap(), window, cx);
  927            assert_eq!(nav_entry.item.id(), cx.entity_id());
  928            assert_eq!(
  929                editor
  930                    .selections
  931                    .display_ranges(&editor.display_snapshot(cx)),
  932                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  933            );
  934            assert!(pop_history(&mut editor, cx).is_none());
  935
  936            // Set scroll position to check later
  937            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  938            let original_scroll_position = editor.scroll_manager.anchor();
  939
  940            // Jump to the end of the document and adjust scroll
  941            editor.move_to_end(&MoveToEnd, window, cx);
  942            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  943            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  944
  945            let nav_entry = pop_history(&mut editor, cx).unwrap();
  946            editor.navigate(nav_entry.data.unwrap(), window, cx);
  947            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  948
  949            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  950            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  951            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  952            let invalid_point = Point::new(9999, 0);
  953            editor.navigate(
  954                Box::new(NavigationData {
  955                    cursor_anchor: invalid_anchor,
  956                    cursor_position: invalid_point,
  957                    scroll_anchor: ScrollAnchor {
  958                        anchor: invalid_anchor,
  959                        offset: Default::default(),
  960                    },
  961                    scroll_top_row: invalid_point.row,
  962                }),
  963                window,
  964                cx,
  965            );
  966            assert_eq!(
  967                editor
  968                    .selections
  969                    .display_ranges(&editor.display_snapshot(cx)),
  970                &[editor.max_point(cx)..editor.max_point(cx)]
  971            );
  972            assert_eq!(
  973                editor.scroll_position(cx),
  974                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  975            );
  976
  977            editor
  978        })
  979    });
  980}
  981
  982#[gpui::test]
  983fn test_cancel(cx: &mut TestAppContext) {
  984    init_test(cx, |_| {});
  985
  986    let editor = cx.add_window(|window, cx| {
  987        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  988        build_editor(buffer, window, cx)
  989    });
  990
  991    _ = editor.update(cx, |editor, window, cx| {
  992        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  993        editor.update_selection(
  994            DisplayPoint::new(DisplayRow(1), 1),
  995            0,
  996            gpui::Point::<f32>::default(),
  997            window,
  998            cx,
  999        );
 1000        editor.end_selection(window, cx);
 1001
 1002        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1003        editor.update_selection(
 1004            DisplayPoint::new(DisplayRow(0), 3),
 1005            0,
 1006            gpui::Point::<f32>::default(),
 1007            window,
 1008            cx,
 1009        );
 1010        editor.end_selection(window, cx);
 1011        assert_eq!(
 1012            display_ranges(editor, cx),
 1013            [
 1014                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1015                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1016            ]
 1017        );
 1018    });
 1019
 1020    _ = editor.update(cx, |editor, window, cx| {
 1021        editor.cancel(&Cancel, window, cx);
 1022        assert_eq!(
 1023            display_ranges(editor, cx),
 1024            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1025        );
 1026    });
 1027
 1028    _ = editor.update(cx, |editor, window, cx| {
 1029        editor.cancel(&Cancel, window, cx);
 1030        assert_eq!(
 1031            display_ranges(editor, cx),
 1032            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1033        );
 1034    });
 1035}
 1036
 1037#[gpui::test]
 1038fn test_fold_action(cx: &mut TestAppContext) {
 1039    init_test(cx, |_| {});
 1040
 1041    let editor = cx.add_window(|window, cx| {
 1042        let buffer = MultiBuffer::build_simple(
 1043            &"
 1044                impl Foo {
 1045                    // Hello!
 1046
 1047                    fn a() {
 1048                        1
 1049                    }
 1050
 1051                    fn b() {
 1052                        2
 1053                    }
 1054
 1055                    fn c() {
 1056                        3
 1057                    }
 1058                }
 1059            "
 1060            .unindent(),
 1061            cx,
 1062        );
 1063        build_editor(buffer, window, cx)
 1064    });
 1065
 1066    _ = editor.update(cx, |editor, window, cx| {
 1067        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1068            s.select_display_ranges([
 1069                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1070            ]);
 1071        });
 1072        editor.fold(&Fold, window, cx);
 1073        assert_eq!(
 1074            editor.display_text(cx),
 1075            "
 1076                impl Foo {
 1077                    // Hello!
 1078
 1079                    fn a() {
 1080                        1
 1081                    }
 1082
 1083                    fn b() {⋯
 1084                    }
 1085
 1086                    fn c() {⋯
 1087                    }
 1088                }
 1089            "
 1090            .unindent(),
 1091        );
 1092
 1093        editor.fold(&Fold, window, cx);
 1094        assert_eq!(
 1095            editor.display_text(cx),
 1096            "
 1097                impl Foo {⋯
 1098                }
 1099            "
 1100            .unindent(),
 1101        );
 1102
 1103        editor.unfold_lines(&UnfoldLines, window, cx);
 1104        assert_eq!(
 1105            editor.display_text(cx),
 1106            "
 1107                impl Foo {
 1108                    // Hello!
 1109
 1110                    fn a() {
 1111                        1
 1112                    }
 1113
 1114                    fn b() {⋯
 1115                    }
 1116
 1117                    fn c() {⋯
 1118                    }
 1119                }
 1120            "
 1121            .unindent(),
 1122        );
 1123
 1124        editor.unfold_lines(&UnfoldLines, window, cx);
 1125        assert_eq!(
 1126            editor.display_text(cx),
 1127            editor.buffer.read(cx).read(cx).text()
 1128        );
 1129    });
 1130}
 1131
 1132#[gpui::test]
 1133fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1134    init_test(cx, |_| {});
 1135
 1136    let editor = cx.add_window(|window, cx| {
 1137        let buffer = MultiBuffer::build_simple(
 1138            &"
 1139                class Foo:
 1140                    # Hello!
 1141
 1142                    def a():
 1143                        print(1)
 1144
 1145                    def b():
 1146                        print(2)
 1147
 1148                    def c():
 1149                        print(3)
 1150            "
 1151            .unindent(),
 1152            cx,
 1153        );
 1154        build_editor(buffer, window, cx)
 1155    });
 1156
 1157    _ = editor.update(cx, |editor, window, cx| {
 1158        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1159            s.select_display_ranges([
 1160                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1161            ]);
 1162        });
 1163        editor.fold(&Fold, window, cx);
 1164        assert_eq!(
 1165            editor.display_text(cx),
 1166            "
 1167                class Foo:
 1168                    # Hello!
 1169
 1170                    def a():
 1171                        print(1)
 1172
 1173                    def b():⋯
 1174
 1175                    def c():⋯
 1176            "
 1177            .unindent(),
 1178        );
 1179
 1180        editor.fold(&Fold, window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:⋯
 1185            "
 1186            .unindent(),
 1187        );
 1188
 1189        editor.unfold_lines(&UnfoldLines, window, cx);
 1190        assert_eq!(
 1191            editor.display_text(cx),
 1192            "
 1193                class Foo:
 1194                    # Hello!
 1195
 1196                    def a():
 1197                        print(1)
 1198
 1199                    def b():⋯
 1200
 1201                    def c():⋯
 1202            "
 1203            .unindent(),
 1204        );
 1205
 1206        editor.unfold_lines(&UnfoldLines, window, cx);
 1207        assert_eq!(
 1208            editor.display_text(cx),
 1209            editor.buffer.read(cx).read(cx).text()
 1210        );
 1211    });
 1212}
 1213
 1214#[gpui::test]
 1215fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1216    init_test(cx, |_| {});
 1217
 1218    let editor = cx.add_window(|window, cx| {
 1219        let buffer = MultiBuffer::build_simple(
 1220            &"
 1221                class Foo:
 1222                    # Hello!
 1223
 1224                    def a():
 1225                        print(1)
 1226
 1227                    def b():
 1228                        print(2)
 1229
 1230
 1231                    def c():
 1232                        print(3)
 1233
 1234
 1235            "
 1236            .unindent(),
 1237            cx,
 1238        );
 1239        build_editor(buffer, window, cx)
 1240    });
 1241
 1242    _ = editor.update(cx, |editor, window, cx| {
 1243        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1244            s.select_display_ranges([
 1245                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1246            ]);
 1247        });
 1248        editor.fold(&Fold, window, cx);
 1249        assert_eq!(
 1250            editor.display_text(cx),
 1251            "
 1252                class Foo:
 1253                    # Hello!
 1254
 1255                    def a():
 1256                        print(1)
 1257
 1258                    def b():⋯
 1259
 1260
 1261                    def c():⋯
 1262
 1263
 1264            "
 1265            .unindent(),
 1266        );
 1267
 1268        editor.fold(&Fold, window, cx);
 1269        assert_eq!(
 1270            editor.display_text(cx),
 1271            "
 1272                class Foo:⋯
 1273
 1274
 1275            "
 1276            .unindent(),
 1277        );
 1278
 1279        editor.unfold_lines(&UnfoldLines, window, cx);
 1280        assert_eq!(
 1281            editor.display_text(cx),
 1282            "
 1283                class Foo:
 1284                    # Hello!
 1285
 1286                    def a():
 1287                        print(1)
 1288
 1289                    def b():⋯
 1290
 1291
 1292                    def c():⋯
 1293
 1294
 1295            "
 1296            .unindent(),
 1297        );
 1298
 1299        editor.unfold_lines(&UnfoldLines, window, cx);
 1300        assert_eq!(
 1301            editor.display_text(cx),
 1302            editor.buffer.read(cx).read(cx).text()
 1303        );
 1304    });
 1305}
 1306
 1307#[gpui::test]
 1308fn test_fold_at_level(cx: &mut TestAppContext) {
 1309    init_test(cx, |_| {});
 1310
 1311    let editor = cx.add_window(|window, cx| {
 1312        let buffer = MultiBuffer::build_simple(
 1313            &"
 1314                class Foo:
 1315                    # Hello!
 1316
 1317                    def a():
 1318                        print(1)
 1319
 1320                    def b():
 1321                        print(2)
 1322
 1323
 1324                class Bar:
 1325                    # World!
 1326
 1327                    def a():
 1328                        print(1)
 1329
 1330                    def b():
 1331                        print(2)
 1332
 1333
 1334            "
 1335            .unindent(),
 1336            cx,
 1337        );
 1338        build_editor(buffer, window, cx)
 1339    });
 1340
 1341    _ = editor.update(cx, |editor, window, cx| {
 1342        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1343        assert_eq!(
 1344            editor.display_text(cx),
 1345            "
 1346                class Foo:
 1347                    # Hello!
 1348
 1349                    def a():⋯
 1350
 1351                    def b():⋯
 1352
 1353
 1354                class Bar:
 1355                    # World!
 1356
 1357                    def a():⋯
 1358
 1359                    def b():⋯
 1360
 1361
 1362            "
 1363            .unindent(),
 1364        );
 1365
 1366        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1367        assert_eq!(
 1368            editor.display_text(cx),
 1369            "
 1370                class Foo:⋯
 1371
 1372
 1373                class Bar:⋯
 1374
 1375
 1376            "
 1377            .unindent(),
 1378        );
 1379
 1380        editor.unfold_all(&UnfoldAll, window, cx);
 1381        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1382        assert_eq!(
 1383            editor.display_text(cx),
 1384            "
 1385                class Foo:
 1386                    # Hello!
 1387
 1388                    def a():
 1389                        print(1)
 1390
 1391                    def b():
 1392                        print(2)
 1393
 1394
 1395                class Bar:
 1396                    # World!
 1397
 1398                    def a():
 1399                        print(1)
 1400
 1401                    def b():
 1402                        print(2)
 1403
 1404
 1405            "
 1406            .unindent(),
 1407        );
 1408
 1409        assert_eq!(
 1410            editor.display_text(cx),
 1411            editor.buffer.read(cx).read(cx).text()
 1412        );
 1413        let (_, positions) = marked_text_ranges(
 1414            &"
 1415                       class Foo:
 1416                           # Hello!
 1417
 1418                           def a():
 1419                              print(1)
 1420
 1421                           def b():
 1422                               p«riˇ»nt(2)
 1423
 1424
 1425                       class Bar:
 1426                           # World!
 1427
 1428                           def a():
 1429                               «ˇprint(1)
 1430
 1431                           def b():
 1432                               print(2)»
 1433
 1434
 1435                   "
 1436            .unindent(),
 1437            true,
 1438        );
 1439
 1440        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1441            s.select_ranges(
 1442                positions
 1443                    .iter()
 1444                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1445            )
 1446        });
 1447
 1448        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1449        assert_eq!(
 1450            editor.display_text(cx),
 1451            "
 1452                class Foo:
 1453                    # Hello!
 1454
 1455                    def a():⋯
 1456
 1457                    def b():
 1458                        print(2)
 1459
 1460
 1461                class Bar:
 1462                    # World!
 1463
 1464                    def a():
 1465                        print(1)
 1466
 1467                    def b():
 1468                        print(2)
 1469
 1470
 1471            "
 1472            .unindent(),
 1473        );
 1474    });
 1475}
 1476
 1477#[gpui::test]
 1478fn test_move_cursor(cx: &mut TestAppContext) {
 1479    init_test(cx, |_| {});
 1480
 1481    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1482    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1483
 1484    buffer.update(cx, |buffer, cx| {
 1485        buffer.edit(
 1486            vec![
 1487                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1488                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1489            ],
 1490            None,
 1491            cx,
 1492        );
 1493    });
 1494    _ = editor.update(cx, |editor, window, cx| {
 1495        assert_eq!(
 1496            display_ranges(editor, cx),
 1497            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1498        );
 1499
 1500        editor.move_down(&MoveDown, window, cx);
 1501        assert_eq!(
 1502            display_ranges(editor, cx),
 1503            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1504        );
 1505
 1506        editor.move_right(&MoveRight, window, cx);
 1507        assert_eq!(
 1508            display_ranges(editor, cx),
 1509            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1510        );
 1511
 1512        editor.move_left(&MoveLeft, window, cx);
 1513        assert_eq!(
 1514            display_ranges(editor, cx),
 1515            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1516        );
 1517
 1518        editor.move_up(&MoveUp, window, cx);
 1519        assert_eq!(
 1520            display_ranges(editor, cx),
 1521            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1522        );
 1523
 1524        editor.move_to_end(&MoveToEnd, window, cx);
 1525        assert_eq!(
 1526            display_ranges(editor, cx),
 1527            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1528        );
 1529
 1530        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1531        assert_eq!(
 1532            display_ranges(editor, cx),
 1533            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1534        );
 1535
 1536        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1537            s.select_display_ranges([
 1538                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1539            ]);
 1540        });
 1541        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1542        assert_eq!(
 1543            display_ranges(editor, cx),
 1544            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1545        );
 1546
 1547        editor.select_to_end(&SelectToEnd, window, cx);
 1548        assert_eq!(
 1549            display_ranges(editor, cx),
 1550            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1551        );
 1552    });
 1553}
 1554
 1555#[gpui::test]
 1556fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1557    init_test(cx, |_| {});
 1558
 1559    let editor = cx.add_window(|window, cx| {
 1560        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1561        build_editor(buffer, window, cx)
 1562    });
 1563
 1564    assert_eq!('🟥'.len_utf8(), 4);
 1565    assert_eq!('α'.len_utf8(), 2);
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.fold_creases(
 1569            vec![
 1570                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1571                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1572                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1573            ],
 1574            true,
 1575            window,
 1576            cx,
 1577        );
 1578        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1579
 1580        editor.move_right(&MoveRight, window, cx);
 1581        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1582        editor.move_right(&MoveRight, window, cx);
 1583        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1584        editor.move_right(&MoveRight, window, cx);
 1585        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1586
 1587        editor.move_down(&MoveDown, window, cx);
 1588        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1589        editor.move_left(&MoveLeft, window, cx);
 1590        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1591        editor.move_left(&MoveLeft, window, cx);
 1592        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1593        editor.move_left(&MoveLeft, window, cx);
 1594        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1595
 1596        editor.move_down(&MoveDown, window, cx);
 1597        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1598        editor.move_right(&MoveRight, window, cx);
 1599        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1600        editor.move_right(&MoveRight, window, cx);
 1601        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1602        editor.move_right(&MoveRight, window, cx);
 1603        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1604
 1605        editor.move_up(&MoveUp, window, cx);
 1606        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1607        editor.move_down(&MoveDown, window, cx);
 1608        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1609        editor.move_up(&MoveUp, window, cx);
 1610        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1611
 1612        editor.move_up(&MoveUp, window, cx);
 1613        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1614        editor.move_left(&MoveLeft, window, cx);
 1615        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1616        editor.move_left(&MoveLeft, window, cx);
 1617        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1618    });
 1619}
 1620
 1621#[gpui::test]
 1622fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1623    init_test(cx, |_| {});
 1624
 1625    let editor = cx.add_window(|window, cx| {
 1626        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1627        build_editor(buffer, window, cx)
 1628    });
 1629    _ = editor.update(cx, |editor, window, cx| {
 1630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1631            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1632        });
 1633
 1634        // moving above start of document should move selection to start of document,
 1635        // but the next move down should still be at the original goal_x
 1636        editor.move_up(&MoveUp, window, cx);
 1637        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1638
 1639        editor.move_down(&MoveDown, window, cx);
 1640        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1641
 1642        editor.move_down(&MoveDown, window, cx);
 1643        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1644
 1645        editor.move_down(&MoveDown, window, cx);
 1646        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1647
 1648        editor.move_down(&MoveDown, window, cx);
 1649        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1650
 1651        // moving past end of document should not change goal_x
 1652        editor.move_down(&MoveDown, window, cx);
 1653        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1654
 1655        editor.move_down(&MoveDown, window, cx);
 1656        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1657
 1658        editor.move_up(&MoveUp, window, cx);
 1659        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1660
 1661        editor.move_up(&MoveUp, window, cx);
 1662        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1663
 1664        editor.move_up(&MoveUp, window, cx);
 1665        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1666    });
 1667}
 1668
 1669#[gpui::test]
 1670fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1671    init_test(cx, |_| {});
 1672    let move_to_beg = MoveToBeginningOfLine {
 1673        stop_at_soft_wraps: true,
 1674        stop_at_indent: true,
 1675    };
 1676
 1677    let delete_to_beg = DeleteToBeginningOfLine {
 1678        stop_at_indent: false,
 1679    };
 1680
 1681    let move_to_end = MoveToEndOfLine {
 1682        stop_at_soft_wraps: true,
 1683    };
 1684
 1685    let editor = cx.add_window(|window, cx| {
 1686        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1687        build_editor(buffer, window, cx)
 1688    });
 1689    _ = editor.update(cx, |editor, window, cx| {
 1690        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1691            s.select_display_ranges([
 1692                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1693                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1694            ]);
 1695        });
 1696    });
 1697
 1698    _ = editor.update(cx, |editor, window, cx| {
 1699        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1700        assert_eq!(
 1701            display_ranges(editor, cx),
 1702            &[
 1703                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1704                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1705            ]
 1706        );
 1707    });
 1708
 1709    _ = editor.update(cx, |editor, window, cx| {
 1710        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1711        assert_eq!(
 1712            display_ranges(editor, cx),
 1713            &[
 1714                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1715                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1716            ]
 1717        );
 1718    });
 1719
 1720    _ = editor.update(cx, |editor, window, cx| {
 1721        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1722        assert_eq!(
 1723            display_ranges(editor, cx),
 1724            &[
 1725                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1726                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1727            ]
 1728        );
 1729    });
 1730
 1731    _ = editor.update(cx, |editor, window, cx| {
 1732        editor.move_to_end_of_line(&move_to_end, window, cx);
 1733        assert_eq!(
 1734            display_ranges(editor, cx),
 1735            &[
 1736                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1737                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1738            ]
 1739        );
 1740    });
 1741
 1742    // Moving to the end of line again is a no-op.
 1743    _ = editor.update(cx, |editor, window, cx| {
 1744        editor.move_to_end_of_line(&move_to_end, window, cx);
 1745        assert_eq!(
 1746            display_ranges(editor, cx),
 1747            &[
 1748                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1749                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1750            ]
 1751        );
 1752    });
 1753
 1754    _ = editor.update(cx, |editor, window, cx| {
 1755        editor.move_left(&MoveLeft, window, cx);
 1756        editor.select_to_beginning_of_line(
 1757            &SelectToBeginningOfLine {
 1758                stop_at_soft_wraps: true,
 1759                stop_at_indent: true,
 1760            },
 1761            window,
 1762            cx,
 1763        );
 1764        assert_eq!(
 1765            display_ranges(editor, cx),
 1766            &[
 1767                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1768                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1769            ]
 1770        );
 1771    });
 1772
 1773    _ = editor.update(cx, |editor, window, cx| {
 1774        editor.select_to_beginning_of_line(
 1775            &SelectToBeginningOfLine {
 1776                stop_at_soft_wraps: true,
 1777                stop_at_indent: true,
 1778            },
 1779            window,
 1780            cx,
 1781        );
 1782        assert_eq!(
 1783            display_ranges(editor, cx),
 1784            &[
 1785                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1786                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1787            ]
 1788        );
 1789    });
 1790
 1791    _ = editor.update(cx, |editor, window, cx| {
 1792        editor.select_to_beginning_of_line(
 1793            &SelectToBeginningOfLine {
 1794                stop_at_soft_wraps: true,
 1795                stop_at_indent: true,
 1796            },
 1797            window,
 1798            cx,
 1799        );
 1800        assert_eq!(
 1801            display_ranges(editor, cx),
 1802            &[
 1803                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1804                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1805            ]
 1806        );
 1807    });
 1808
 1809    _ = editor.update(cx, |editor, window, cx| {
 1810        editor.select_to_end_of_line(
 1811            &SelectToEndOfLine {
 1812                stop_at_soft_wraps: true,
 1813            },
 1814            window,
 1815            cx,
 1816        );
 1817        assert_eq!(
 1818            display_ranges(editor, cx),
 1819            &[
 1820                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1821                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1822            ]
 1823        );
 1824    });
 1825
 1826    _ = editor.update(cx, |editor, window, cx| {
 1827        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1828        assert_eq!(editor.display_text(cx), "ab\n  de");
 1829        assert_eq!(
 1830            display_ranges(editor, cx),
 1831            &[
 1832                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1833                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1834            ]
 1835        );
 1836    });
 1837
 1838    _ = editor.update(cx, |editor, window, cx| {
 1839        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1840        assert_eq!(editor.display_text(cx), "\n");
 1841        assert_eq!(
 1842            display_ranges(editor, cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1846            ]
 1847        );
 1848    });
 1849}
 1850
 1851#[gpui::test]
 1852fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1853    init_test(cx, |_| {});
 1854    let move_to_beg = MoveToBeginningOfLine {
 1855        stop_at_soft_wraps: false,
 1856        stop_at_indent: false,
 1857    };
 1858
 1859    let move_to_end = MoveToEndOfLine {
 1860        stop_at_soft_wraps: false,
 1861    };
 1862
 1863    let editor = cx.add_window(|window, cx| {
 1864        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1865        build_editor(buffer, window, cx)
 1866    });
 1867
 1868    _ = editor.update(cx, |editor, window, cx| {
 1869        editor.set_wrap_width(Some(140.0.into()), cx);
 1870
 1871        // We expect the following lines after wrapping
 1872        // ```
 1873        // thequickbrownfox
 1874        // jumpedoverthelazydo
 1875        // gs
 1876        // ```
 1877        // The final `gs` was soft-wrapped onto a new line.
 1878        assert_eq!(
 1879            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1880            editor.display_text(cx),
 1881        );
 1882
 1883        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1884        // Start the cursor at the `k` on the first line
 1885        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1886            s.select_display_ranges([
 1887                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1888            ]);
 1889        });
 1890
 1891        // Moving to the beginning of the line should put us at the beginning of the line.
 1892        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1893        assert_eq!(
 1894            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1895            display_ranges(editor, cx)
 1896        );
 1897
 1898        // Moving to the end of the line should put us at the end of the line.
 1899        editor.move_to_end_of_line(&move_to_end, window, cx);
 1900        assert_eq!(
 1901            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1902            display_ranges(editor, cx)
 1903        );
 1904
 1905        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1906        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1908            s.select_display_ranges([
 1909                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1910            ]);
 1911        });
 1912
 1913        // Moving to the beginning of the line should put us at the start of the second line of
 1914        // display text, i.e., the `j`.
 1915        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1916        assert_eq!(
 1917            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1918            display_ranges(editor, cx)
 1919        );
 1920
 1921        // Moving to the beginning of the line again should be a no-op.
 1922        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1923        assert_eq!(
 1924            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1925            display_ranges(editor, cx)
 1926        );
 1927
 1928        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1929        // next display line.
 1930        editor.move_to_end_of_line(&move_to_end, window, cx);
 1931        assert_eq!(
 1932            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1933            display_ranges(editor, cx)
 1934        );
 1935
 1936        // Moving to the end of the line again should be a no-op.
 1937        editor.move_to_end_of_line(&move_to_end, window, cx);
 1938        assert_eq!(
 1939            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1940            display_ranges(editor, cx)
 1941        );
 1942    });
 1943}
 1944
 1945#[gpui::test]
 1946fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1947    init_test(cx, |_| {});
 1948
 1949    let move_to_beg = MoveToBeginningOfLine {
 1950        stop_at_soft_wraps: true,
 1951        stop_at_indent: true,
 1952    };
 1953
 1954    let select_to_beg = SelectToBeginningOfLine {
 1955        stop_at_soft_wraps: true,
 1956        stop_at_indent: true,
 1957    };
 1958
 1959    let delete_to_beg = DeleteToBeginningOfLine {
 1960        stop_at_indent: true,
 1961    };
 1962
 1963    let move_to_end = MoveToEndOfLine {
 1964        stop_at_soft_wraps: false,
 1965    };
 1966
 1967    let editor = cx.add_window(|window, cx| {
 1968        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1969        build_editor(buffer, window, cx)
 1970    });
 1971
 1972    _ = editor.update(cx, |editor, window, cx| {
 1973        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1974            s.select_display_ranges([
 1975                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1976                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1977            ]);
 1978        });
 1979
 1980        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1981        // and the second cursor at the first non-whitespace character in the line.
 1982        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1983        assert_eq!(
 1984            display_ranges(editor, cx),
 1985            &[
 1986                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1987                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1988            ]
 1989        );
 1990
 1991        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1992        // and should move the second cursor to the beginning of the line.
 1993        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1994        assert_eq!(
 1995            display_ranges(editor, cx),
 1996            &[
 1997                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1998                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1999            ]
 2000        );
 2001
 2002        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2003        // and should move the second cursor back to the first non-whitespace character in the line.
 2004        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2005        assert_eq!(
 2006            display_ranges(editor, cx),
 2007            &[
 2008                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2009                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2010            ]
 2011        );
 2012
 2013        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2014        // and to the first non-whitespace character in the line for the second cursor.
 2015        editor.move_to_end_of_line(&move_to_end, window, cx);
 2016        editor.move_left(&MoveLeft, window, cx);
 2017        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2018        assert_eq!(
 2019            display_ranges(editor, cx),
 2020            &[
 2021                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2022                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2023            ]
 2024        );
 2025
 2026        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2027        // and should select to the beginning of the line for the second cursor.
 2028        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2029        assert_eq!(
 2030            display_ranges(editor, cx),
 2031            &[
 2032                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2033                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2034            ]
 2035        );
 2036
 2037        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2038        // and should delete to the first non-whitespace character in the line for the second cursor.
 2039        editor.move_to_end_of_line(&move_to_end, window, cx);
 2040        editor.move_left(&MoveLeft, window, cx);
 2041        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2042        assert_eq!(editor.text(cx), "c\n  f");
 2043    });
 2044}
 2045
 2046#[gpui::test]
 2047fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2048    init_test(cx, |_| {});
 2049
 2050    let move_to_beg = MoveToBeginningOfLine {
 2051        stop_at_soft_wraps: true,
 2052        stop_at_indent: true,
 2053    };
 2054
 2055    let editor = cx.add_window(|window, cx| {
 2056        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2057        build_editor(buffer, window, cx)
 2058    });
 2059
 2060    _ = editor.update(cx, |editor, window, cx| {
 2061        // test cursor between line_start and indent_start
 2062        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2063            s.select_display_ranges([
 2064                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2065            ]);
 2066        });
 2067
 2068        // cursor should move to line_start
 2069        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2070        assert_eq!(
 2071            display_ranges(editor, cx),
 2072            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2073        );
 2074
 2075        // cursor should move to indent_start
 2076        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2077        assert_eq!(
 2078            display_ranges(editor, cx),
 2079            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2080        );
 2081
 2082        // cursor should move to back to line_start
 2083        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2084        assert_eq!(
 2085            display_ranges(editor, cx),
 2086            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2087        );
 2088    });
 2089}
 2090
 2091#[gpui::test]
 2092fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2093    init_test(cx, |_| {});
 2094
 2095    let editor = cx.add_window(|window, cx| {
 2096        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2097        build_editor(buffer, window, cx)
 2098    });
 2099    _ = editor.update(cx, |editor, window, cx| {
 2100        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2101            s.select_display_ranges([
 2102                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2103                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2104            ])
 2105        });
 2106        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2107        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2108
 2109        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2110        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2111
 2112        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2113        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2114
 2115        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2116        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2117
 2118        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2119        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2120
 2121        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2122        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2123
 2124        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2125        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2126
 2127        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2128        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2129
 2130        editor.move_right(&MoveRight, window, cx);
 2131        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2132        assert_selection_ranges(
 2133            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2134            editor,
 2135            cx,
 2136        );
 2137
 2138        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2139        assert_selection_ranges(
 2140            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2141            editor,
 2142            cx,
 2143        );
 2144
 2145        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2146        assert_selection_ranges(
 2147            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2148            editor,
 2149            cx,
 2150        );
 2151    });
 2152}
 2153
 2154#[gpui::test]
 2155fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2156    init_test(cx, |_| {});
 2157
 2158    let editor = cx.add_window(|window, cx| {
 2159        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2160        build_editor(buffer, window, cx)
 2161    });
 2162
 2163    _ = editor.update(cx, |editor, window, cx| {
 2164        editor.set_wrap_width(Some(140.0.into()), cx);
 2165        assert_eq!(
 2166            editor.display_text(cx),
 2167            "use one::{\n    two::three::\n    four::five\n};"
 2168        );
 2169
 2170        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2171            s.select_display_ranges([
 2172                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2173            ]);
 2174        });
 2175
 2176        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2177        assert_eq!(
 2178            display_ranges(editor, cx),
 2179            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2180        );
 2181
 2182        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2183        assert_eq!(
 2184            display_ranges(editor, cx),
 2185            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2186        );
 2187
 2188        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2189        assert_eq!(
 2190            display_ranges(editor, cx),
 2191            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2192        );
 2193
 2194        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2195        assert_eq!(
 2196            display_ranges(editor, cx),
 2197            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2198        );
 2199
 2200        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2201        assert_eq!(
 2202            display_ranges(editor, cx),
 2203            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2204        );
 2205
 2206        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2207        assert_eq!(
 2208            display_ranges(editor, cx),
 2209            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2210        );
 2211    });
 2212}
 2213
 2214#[gpui::test]
 2215async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2216    init_test(cx, |_| {});
 2217    let mut cx = EditorTestContext::new(cx).await;
 2218
 2219    let line_height = cx.editor(|editor, window, _| {
 2220        editor
 2221            .style()
 2222            .unwrap()
 2223            .text
 2224            .line_height_in_pixels(window.rem_size())
 2225    });
 2226    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2227
 2228    cx.set_state(
 2229        &r#"ˇone
 2230        two
 2231
 2232        three
 2233        fourˇ
 2234        five
 2235
 2236        six"#
 2237            .unindent(),
 2238    );
 2239
 2240    cx.update_editor(|editor, window, cx| {
 2241        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2242    });
 2243    cx.assert_editor_state(
 2244        &r#"one
 2245        two
 2246        ˇ
 2247        three
 2248        four
 2249        five
 2250        ˇ
 2251        six"#
 2252            .unindent(),
 2253    );
 2254
 2255    cx.update_editor(|editor, window, cx| {
 2256        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2257    });
 2258    cx.assert_editor_state(
 2259        &r#"one
 2260        two
 2261
 2262        three
 2263        four
 2264        five
 2265        ˇ
 2266        sixˇ"#
 2267            .unindent(),
 2268    );
 2269
 2270    cx.update_editor(|editor, window, cx| {
 2271        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2272    });
 2273    cx.assert_editor_state(
 2274        &r#"one
 2275        two
 2276
 2277        three
 2278        four
 2279        five
 2280
 2281        sixˇ"#
 2282            .unindent(),
 2283    );
 2284
 2285    cx.update_editor(|editor, window, cx| {
 2286        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2287    });
 2288    cx.assert_editor_state(
 2289        &r#"one
 2290        two
 2291
 2292        three
 2293        four
 2294        five
 2295        ˇ
 2296        six"#
 2297            .unindent(),
 2298    );
 2299
 2300    cx.update_editor(|editor, window, cx| {
 2301        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2302    });
 2303    cx.assert_editor_state(
 2304        &r#"one
 2305        two
 2306        ˇ
 2307        three
 2308        four
 2309        five
 2310
 2311        six"#
 2312            .unindent(),
 2313    );
 2314
 2315    cx.update_editor(|editor, window, cx| {
 2316        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2317    });
 2318    cx.assert_editor_state(
 2319        &r#"ˇone
 2320        two
 2321
 2322        three
 2323        four
 2324        five
 2325
 2326        six"#
 2327            .unindent(),
 2328    );
 2329}
 2330
 2331#[gpui::test]
 2332async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2333    init_test(cx, |_| {});
 2334    let mut cx = EditorTestContext::new(cx).await;
 2335    let line_height = cx.editor(|editor, window, _| {
 2336        editor
 2337            .style()
 2338            .unwrap()
 2339            .text
 2340            .line_height_in_pixels(window.rem_size())
 2341    });
 2342    let window = cx.window;
 2343    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2344
 2345    cx.set_state(
 2346        r#"ˇone
 2347        two
 2348        three
 2349        four
 2350        five
 2351        six
 2352        seven
 2353        eight
 2354        nine
 2355        ten
 2356        "#,
 2357    );
 2358
 2359    cx.update_editor(|editor, window, cx| {
 2360        assert_eq!(
 2361            editor.snapshot(window, cx).scroll_position(),
 2362            gpui::Point::new(0., 0.)
 2363        );
 2364        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2365        assert_eq!(
 2366            editor.snapshot(window, cx).scroll_position(),
 2367            gpui::Point::new(0., 3.)
 2368        );
 2369        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2370        assert_eq!(
 2371            editor.snapshot(window, cx).scroll_position(),
 2372            gpui::Point::new(0., 6.)
 2373        );
 2374        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2375        assert_eq!(
 2376            editor.snapshot(window, cx).scroll_position(),
 2377            gpui::Point::new(0., 3.)
 2378        );
 2379
 2380        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2381        assert_eq!(
 2382            editor.snapshot(window, cx).scroll_position(),
 2383            gpui::Point::new(0., 1.)
 2384        );
 2385        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2386        assert_eq!(
 2387            editor.snapshot(window, cx).scroll_position(),
 2388            gpui::Point::new(0., 3.)
 2389        );
 2390    });
 2391}
 2392
 2393#[gpui::test]
 2394async fn test_autoscroll(cx: &mut TestAppContext) {
 2395    init_test(cx, |_| {});
 2396    let mut cx = EditorTestContext::new(cx).await;
 2397
 2398    let line_height = cx.update_editor(|editor, window, cx| {
 2399        editor.set_vertical_scroll_margin(2, cx);
 2400        editor
 2401            .style()
 2402            .unwrap()
 2403            .text
 2404            .line_height_in_pixels(window.rem_size())
 2405    });
 2406    let window = cx.window;
 2407    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2408
 2409    cx.set_state(
 2410        r#"ˇone
 2411            two
 2412            three
 2413            four
 2414            five
 2415            six
 2416            seven
 2417            eight
 2418            nine
 2419            ten
 2420        "#,
 2421    );
 2422    cx.update_editor(|editor, window, cx| {
 2423        assert_eq!(
 2424            editor.snapshot(window, cx).scroll_position(),
 2425            gpui::Point::new(0., 0.0)
 2426        );
 2427    });
 2428
 2429    // Add a cursor below the visible area. Since both cursors cannot fit
 2430    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2431    // allows the vertical scroll margin below that cursor.
 2432    cx.update_editor(|editor, window, cx| {
 2433        editor.change_selections(Default::default(), window, cx, |selections| {
 2434            selections.select_ranges([
 2435                Point::new(0, 0)..Point::new(0, 0),
 2436                Point::new(6, 0)..Point::new(6, 0),
 2437            ]);
 2438        })
 2439    });
 2440    cx.update_editor(|editor, window, cx| {
 2441        assert_eq!(
 2442            editor.snapshot(window, cx).scroll_position(),
 2443            gpui::Point::new(0., 3.0)
 2444        );
 2445    });
 2446
 2447    // Move down. The editor cursor scrolls down to track the newest cursor.
 2448    cx.update_editor(|editor, window, cx| {
 2449        editor.move_down(&Default::default(), window, cx);
 2450    });
 2451    cx.update_editor(|editor, window, cx| {
 2452        assert_eq!(
 2453            editor.snapshot(window, cx).scroll_position(),
 2454            gpui::Point::new(0., 4.0)
 2455        );
 2456    });
 2457
 2458    // Add a cursor above the visible area. Since both cursors fit on screen,
 2459    // the editor scrolls to show both.
 2460    cx.update_editor(|editor, window, cx| {
 2461        editor.change_selections(Default::default(), window, cx, |selections| {
 2462            selections.select_ranges([
 2463                Point::new(1, 0)..Point::new(1, 0),
 2464                Point::new(6, 0)..Point::new(6, 0),
 2465            ]);
 2466        })
 2467    });
 2468    cx.update_editor(|editor, window, cx| {
 2469        assert_eq!(
 2470            editor.snapshot(window, cx).scroll_position(),
 2471            gpui::Point::new(0., 1.0)
 2472        );
 2473    });
 2474}
 2475
 2476#[gpui::test]
 2477async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2478    init_test(cx, |_| {});
 2479    let mut cx = EditorTestContext::new(cx).await;
 2480
 2481    let line_height = cx.editor(|editor, window, _cx| {
 2482        editor
 2483            .style()
 2484            .unwrap()
 2485            .text
 2486            .line_height_in_pixels(window.rem_size())
 2487    });
 2488    let window = cx.window;
 2489    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2490    cx.set_state(
 2491        &r#"
 2492        ˇone
 2493        two
 2494        threeˇ
 2495        four
 2496        five
 2497        six
 2498        seven
 2499        eight
 2500        nine
 2501        ten
 2502        "#
 2503        .unindent(),
 2504    );
 2505
 2506    cx.update_editor(|editor, window, cx| {
 2507        editor.move_page_down(&MovePageDown::default(), window, cx)
 2508    });
 2509    cx.assert_editor_state(
 2510        &r#"
 2511        one
 2512        two
 2513        three
 2514        ˇfour
 2515        five
 2516        sixˇ
 2517        seven
 2518        eight
 2519        nine
 2520        ten
 2521        "#
 2522        .unindent(),
 2523    );
 2524
 2525    cx.update_editor(|editor, window, cx| {
 2526        editor.move_page_down(&MovePageDown::default(), window, cx)
 2527    });
 2528    cx.assert_editor_state(
 2529        &r#"
 2530        one
 2531        two
 2532        three
 2533        four
 2534        five
 2535        six
 2536        ˇseven
 2537        eight
 2538        nineˇ
 2539        ten
 2540        "#
 2541        .unindent(),
 2542    );
 2543
 2544    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2545    cx.assert_editor_state(
 2546        &r#"
 2547        one
 2548        two
 2549        three
 2550        ˇfour
 2551        five
 2552        sixˇ
 2553        seven
 2554        eight
 2555        nine
 2556        ten
 2557        "#
 2558        .unindent(),
 2559    );
 2560
 2561    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2562    cx.assert_editor_state(
 2563        &r#"
 2564        ˇone
 2565        two
 2566        threeˇ
 2567        four
 2568        five
 2569        six
 2570        seven
 2571        eight
 2572        nine
 2573        ten
 2574        "#
 2575        .unindent(),
 2576    );
 2577
 2578    // Test select collapsing
 2579    cx.update_editor(|editor, window, cx| {
 2580        editor.move_page_down(&MovePageDown::default(), window, cx);
 2581        editor.move_page_down(&MovePageDown::default(), window, cx);
 2582        editor.move_page_down(&MovePageDown::default(), window, cx);
 2583    });
 2584    cx.assert_editor_state(
 2585        &r#"
 2586        one
 2587        two
 2588        three
 2589        four
 2590        five
 2591        six
 2592        seven
 2593        eight
 2594        nine
 2595        ˇten
 2596        ˇ"#
 2597        .unindent(),
 2598    );
 2599}
 2600
 2601#[gpui::test]
 2602async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2603    init_test(cx, |_| {});
 2604    let mut cx = EditorTestContext::new(cx).await;
 2605    cx.set_state("one «two threeˇ» four");
 2606    cx.update_editor(|editor, window, cx| {
 2607        editor.delete_to_beginning_of_line(
 2608            &DeleteToBeginningOfLine {
 2609                stop_at_indent: false,
 2610            },
 2611            window,
 2612            cx,
 2613        );
 2614        assert_eq!(editor.text(cx), " four");
 2615    });
 2616}
 2617
 2618#[gpui::test]
 2619async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2620    init_test(cx, |_| {});
 2621
 2622    let mut cx = EditorTestContext::new(cx).await;
 2623
 2624    // For an empty selection, the preceding word fragment is deleted.
 2625    // For non-empty selections, only selected characters are deleted.
 2626    cx.set_state("onˇe two t«hreˇ»e four");
 2627    cx.update_editor(|editor, window, cx| {
 2628        editor.delete_to_previous_word_start(
 2629            &DeleteToPreviousWordStart {
 2630                ignore_newlines: false,
 2631                ignore_brackets: false,
 2632            },
 2633            window,
 2634            cx,
 2635        );
 2636    });
 2637    cx.assert_editor_state("ˇe two tˇe four");
 2638
 2639    cx.set_state("e tˇwo te «fˇ»our");
 2640    cx.update_editor(|editor, window, cx| {
 2641        editor.delete_to_next_word_end(
 2642            &DeleteToNextWordEnd {
 2643                ignore_newlines: false,
 2644                ignore_brackets: false,
 2645            },
 2646            window,
 2647            cx,
 2648        );
 2649    });
 2650    cx.assert_editor_state("e tˇ te ˇour");
 2651}
 2652
 2653#[gpui::test]
 2654async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2655    init_test(cx, |_| {});
 2656
 2657    let mut cx = EditorTestContext::new(cx).await;
 2658
 2659    cx.set_state("here is some text    ˇwith a space");
 2660    cx.update_editor(|editor, window, cx| {
 2661        editor.delete_to_previous_word_start(
 2662            &DeleteToPreviousWordStart {
 2663                ignore_newlines: false,
 2664                ignore_brackets: true,
 2665            },
 2666            window,
 2667            cx,
 2668        );
 2669    });
 2670    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2671    cx.assert_editor_state("here is some textˇwith a space");
 2672
 2673    cx.set_state("here is some text    ˇwith a space");
 2674    cx.update_editor(|editor, window, cx| {
 2675        editor.delete_to_previous_word_start(
 2676            &DeleteToPreviousWordStart {
 2677                ignore_newlines: false,
 2678                ignore_brackets: false,
 2679            },
 2680            window,
 2681            cx,
 2682        );
 2683    });
 2684    cx.assert_editor_state("here is some textˇwith a space");
 2685
 2686    cx.set_state("here is some textˇ    with a space");
 2687    cx.update_editor(|editor, window, cx| {
 2688        editor.delete_to_next_word_end(
 2689            &DeleteToNextWordEnd {
 2690                ignore_newlines: false,
 2691                ignore_brackets: true,
 2692            },
 2693            window,
 2694            cx,
 2695        );
 2696    });
 2697    // Same happens in the other direction.
 2698    cx.assert_editor_state("here is some textˇwith a space");
 2699
 2700    cx.set_state("here is some textˇ    with a space");
 2701    cx.update_editor(|editor, window, cx| {
 2702        editor.delete_to_next_word_end(
 2703            &DeleteToNextWordEnd {
 2704                ignore_newlines: false,
 2705                ignore_brackets: false,
 2706            },
 2707            window,
 2708            cx,
 2709        );
 2710    });
 2711    cx.assert_editor_state("here is some textˇwith a space");
 2712
 2713    cx.set_state("here is some textˇ    with a space");
 2714    cx.update_editor(|editor, window, cx| {
 2715        editor.delete_to_next_word_end(
 2716            &DeleteToNextWordEnd {
 2717                ignore_newlines: true,
 2718                ignore_brackets: false,
 2719            },
 2720            window,
 2721            cx,
 2722        );
 2723    });
 2724    cx.assert_editor_state("here is some textˇwith a space");
 2725    cx.update_editor(|editor, window, cx| {
 2726        editor.delete_to_previous_word_start(
 2727            &DeleteToPreviousWordStart {
 2728                ignore_newlines: true,
 2729                ignore_brackets: false,
 2730            },
 2731            window,
 2732            cx,
 2733        );
 2734    });
 2735    cx.assert_editor_state("here is some ˇwith a space");
 2736    cx.update_editor(|editor, window, cx| {
 2737        editor.delete_to_previous_word_start(
 2738            &DeleteToPreviousWordStart {
 2739                ignore_newlines: true,
 2740                ignore_brackets: false,
 2741            },
 2742            window,
 2743            cx,
 2744        );
 2745    });
 2746    // Single whitespaces are removed with the word behind them.
 2747    cx.assert_editor_state("here is ˇwith a space");
 2748    cx.update_editor(|editor, window, cx| {
 2749        editor.delete_to_previous_word_start(
 2750            &DeleteToPreviousWordStart {
 2751                ignore_newlines: true,
 2752                ignore_brackets: false,
 2753            },
 2754            window,
 2755            cx,
 2756        );
 2757    });
 2758    cx.assert_editor_state("here ˇwith a space");
 2759    cx.update_editor(|editor, window, cx| {
 2760        editor.delete_to_previous_word_start(
 2761            &DeleteToPreviousWordStart {
 2762                ignore_newlines: true,
 2763                ignore_brackets: false,
 2764            },
 2765            window,
 2766            cx,
 2767        );
 2768    });
 2769    cx.assert_editor_state("ˇwith a space");
 2770    cx.update_editor(|editor, window, cx| {
 2771        editor.delete_to_previous_word_start(
 2772            &DeleteToPreviousWordStart {
 2773                ignore_newlines: true,
 2774                ignore_brackets: false,
 2775            },
 2776            window,
 2777            cx,
 2778        );
 2779    });
 2780    cx.assert_editor_state("ˇwith a space");
 2781    cx.update_editor(|editor, window, cx| {
 2782        editor.delete_to_next_word_end(
 2783            &DeleteToNextWordEnd {
 2784                ignore_newlines: true,
 2785                ignore_brackets: false,
 2786            },
 2787            window,
 2788            cx,
 2789        );
 2790    });
 2791    // Same happens in the other direction.
 2792    cx.assert_editor_state("ˇ a space");
 2793    cx.update_editor(|editor, window, cx| {
 2794        editor.delete_to_next_word_end(
 2795            &DeleteToNextWordEnd {
 2796                ignore_newlines: true,
 2797                ignore_brackets: false,
 2798            },
 2799            window,
 2800            cx,
 2801        );
 2802    });
 2803    cx.assert_editor_state("ˇ space");
 2804    cx.update_editor(|editor, window, cx| {
 2805        editor.delete_to_next_word_end(
 2806            &DeleteToNextWordEnd {
 2807                ignore_newlines: true,
 2808                ignore_brackets: false,
 2809            },
 2810            window,
 2811            cx,
 2812        );
 2813    });
 2814    cx.assert_editor_state("ˇ");
 2815    cx.update_editor(|editor, window, cx| {
 2816        editor.delete_to_next_word_end(
 2817            &DeleteToNextWordEnd {
 2818                ignore_newlines: true,
 2819                ignore_brackets: false,
 2820            },
 2821            window,
 2822            cx,
 2823        );
 2824    });
 2825    cx.assert_editor_state("ˇ");
 2826    cx.update_editor(|editor, window, cx| {
 2827        editor.delete_to_previous_word_start(
 2828            &DeleteToPreviousWordStart {
 2829                ignore_newlines: true,
 2830                ignore_brackets: false,
 2831            },
 2832            window,
 2833            cx,
 2834        );
 2835    });
 2836    cx.assert_editor_state("ˇ");
 2837}
 2838
 2839#[gpui::test]
 2840async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2841    init_test(cx, |_| {});
 2842
 2843    let language = Arc::new(
 2844        Language::new(
 2845            LanguageConfig {
 2846                brackets: BracketPairConfig {
 2847                    pairs: vec![
 2848                        BracketPair {
 2849                            start: "\"".to_string(),
 2850                            end: "\"".to_string(),
 2851                            close: true,
 2852                            surround: true,
 2853                            newline: false,
 2854                        },
 2855                        BracketPair {
 2856                            start: "(".to_string(),
 2857                            end: ")".to_string(),
 2858                            close: true,
 2859                            surround: true,
 2860                            newline: true,
 2861                        },
 2862                    ],
 2863                    ..BracketPairConfig::default()
 2864                },
 2865                ..LanguageConfig::default()
 2866            },
 2867            Some(tree_sitter_rust::LANGUAGE.into()),
 2868        )
 2869        .with_brackets_query(
 2870            r#"
 2871                ("(" @open ")" @close)
 2872                ("\"" @open "\"" @close)
 2873            "#,
 2874        )
 2875        .unwrap(),
 2876    );
 2877
 2878    let mut cx = EditorTestContext::new(cx).await;
 2879    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2880
 2881    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2882    cx.update_editor(|editor, window, cx| {
 2883        editor.delete_to_previous_word_start(
 2884            &DeleteToPreviousWordStart {
 2885                ignore_newlines: true,
 2886                ignore_brackets: false,
 2887            },
 2888            window,
 2889            cx,
 2890        );
 2891    });
 2892    // Deletion stops before brackets if asked to not ignore them.
 2893    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2894    cx.update_editor(|editor, window, cx| {
 2895        editor.delete_to_previous_word_start(
 2896            &DeleteToPreviousWordStart {
 2897                ignore_newlines: true,
 2898                ignore_brackets: false,
 2899            },
 2900            window,
 2901            cx,
 2902        );
 2903    });
 2904    // Deletion has to remove a single bracket and then stop again.
 2905    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2906
 2907    cx.update_editor(|editor, window, cx| {
 2908        editor.delete_to_previous_word_start(
 2909            &DeleteToPreviousWordStart {
 2910                ignore_newlines: true,
 2911                ignore_brackets: false,
 2912            },
 2913            window,
 2914            cx,
 2915        );
 2916    });
 2917    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2918
 2919    cx.update_editor(|editor, window, cx| {
 2920        editor.delete_to_previous_word_start(
 2921            &DeleteToPreviousWordStart {
 2922                ignore_newlines: true,
 2923                ignore_brackets: false,
 2924            },
 2925            window,
 2926            cx,
 2927        );
 2928    });
 2929    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2930
 2931    cx.update_editor(|editor, window, cx| {
 2932        editor.delete_to_previous_word_start(
 2933            &DeleteToPreviousWordStart {
 2934                ignore_newlines: true,
 2935                ignore_brackets: false,
 2936            },
 2937            window,
 2938            cx,
 2939        );
 2940    });
 2941    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2942
 2943    cx.update_editor(|editor, window, cx| {
 2944        editor.delete_to_next_word_end(
 2945            &DeleteToNextWordEnd {
 2946                ignore_newlines: true,
 2947                ignore_brackets: false,
 2948            },
 2949            window,
 2950            cx,
 2951        );
 2952    });
 2953    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2954    cx.assert_editor_state(r#"ˇ");"#);
 2955
 2956    cx.update_editor(|editor, window, cx| {
 2957        editor.delete_to_next_word_end(
 2958            &DeleteToNextWordEnd {
 2959                ignore_newlines: true,
 2960                ignore_brackets: false,
 2961            },
 2962            window,
 2963            cx,
 2964        );
 2965    });
 2966    cx.assert_editor_state(r#"ˇ"#);
 2967
 2968    cx.update_editor(|editor, window, cx| {
 2969        editor.delete_to_next_word_end(
 2970            &DeleteToNextWordEnd {
 2971                ignore_newlines: true,
 2972                ignore_brackets: false,
 2973            },
 2974            window,
 2975            cx,
 2976        );
 2977    });
 2978    cx.assert_editor_state(r#"ˇ"#);
 2979
 2980    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2981    cx.update_editor(|editor, window, cx| {
 2982        editor.delete_to_previous_word_start(
 2983            &DeleteToPreviousWordStart {
 2984                ignore_newlines: true,
 2985                ignore_brackets: true,
 2986            },
 2987            window,
 2988            cx,
 2989        );
 2990    });
 2991    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2992}
 2993
 2994#[gpui::test]
 2995fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2996    init_test(cx, |_| {});
 2997
 2998    let editor = cx.add_window(|window, cx| {
 2999        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3000        build_editor(buffer, window, cx)
 3001    });
 3002    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3003        ignore_newlines: false,
 3004        ignore_brackets: false,
 3005    };
 3006    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3007        ignore_newlines: true,
 3008        ignore_brackets: false,
 3009    };
 3010
 3011    _ = editor.update(cx, |editor, window, cx| {
 3012        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3013            s.select_display_ranges([
 3014                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3015            ])
 3016        });
 3017        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3018        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3019        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3020        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3021        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3022        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3023        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3024        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3025        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3026        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3027        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3028        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3029    });
 3030}
 3031
 3032#[gpui::test]
 3033fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3034    init_test(cx, |_| {});
 3035
 3036    let editor = cx.add_window(|window, cx| {
 3037        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3038        build_editor(buffer, window, cx)
 3039    });
 3040    let del_to_next_word_end = DeleteToNextWordEnd {
 3041        ignore_newlines: false,
 3042        ignore_brackets: false,
 3043    };
 3044    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3045        ignore_newlines: true,
 3046        ignore_brackets: false,
 3047    };
 3048
 3049    _ = editor.update(cx, |editor, window, cx| {
 3050        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3051            s.select_display_ranges([
 3052                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3053            ])
 3054        });
 3055        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3056        assert_eq!(
 3057            editor.buffer.read(cx).read(cx).text(),
 3058            "one\n   two\nthree\n   four"
 3059        );
 3060        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3061        assert_eq!(
 3062            editor.buffer.read(cx).read(cx).text(),
 3063            "\n   two\nthree\n   four"
 3064        );
 3065        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3066        assert_eq!(
 3067            editor.buffer.read(cx).read(cx).text(),
 3068            "two\nthree\n   four"
 3069        );
 3070        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3071        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3072        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3073        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3074        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3075        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3076        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3077        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3078    });
 3079}
 3080
 3081#[gpui::test]
 3082fn test_newline(cx: &mut TestAppContext) {
 3083    init_test(cx, |_| {});
 3084
 3085    let editor = cx.add_window(|window, cx| {
 3086        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3087        build_editor(buffer, window, cx)
 3088    });
 3089
 3090    _ = editor.update(cx, |editor, window, cx| {
 3091        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3092            s.select_display_ranges([
 3093                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3094                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3095                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3096            ])
 3097        });
 3098
 3099        editor.newline(&Newline, window, cx);
 3100        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3101    });
 3102}
 3103
 3104#[gpui::test]
 3105async fn test_newline_yaml(cx: &mut TestAppContext) {
 3106    init_test(cx, |_| {});
 3107
 3108    let mut cx = EditorTestContext::new(cx).await;
 3109    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3110    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3111
 3112    // Object (between 2 fields)
 3113    cx.set_state(indoc! {"
 3114    test:ˇ
 3115    hello: bye"});
 3116    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3117    cx.assert_editor_state(indoc! {"
 3118    test:
 3119        ˇ
 3120    hello: bye"});
 3121
 3122    // Object (first and single line)
 3123    cx.set_state(indoc! {"
 3124    test:ˇ"});
 3125    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3126    cx.assert_editor_state(indoc! {"
 3127    test:
 3128        ˇ"});
 3129
 3130    // Array with objects (after first element)
 3131    cx.set_state(indoc! {"
 3132    test:
 3133        - foo: barˇ"});
 3134    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3135    cx.assert_editor_state(indoc! {"
 3136    test:
 3137        - foo: bar
 3138        ˇ"});
 3139
 3140    // Array with objects and comment
 3141    cx.set_state(indoc! {"
 3142    test:
 3143        - foo: bar
 3144        - bar: # testˇ"});
 3145    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3146    cx.assert_editor_state(indoc! {"
 3147    test:
 3148        - foo: bar
 3149        - bar: # test
 3150            ˇ"});
 3151
 3152    // Array with objects (after second element)
 3153    cx.set_state(indoc! {"
 3154    test:
 3155        - foo: bar
 3156        - bar: fooˇ"});
 3157    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3158    cx.assert_editor_state(indoc! {"
 3159    test:
 3160        - foo: bar
 3161        - bar: foo
 3162        ˇ"});
 3163
 3164    // Array with strings (after first element)
 3165    cx.set_state(indoc! {"
 3166    test:
 3167        - fooˇ"});
 3168    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3169    cx.assert_editor_state(indoc! {"
 3170    test:
 3171        - foo
 3172        ˇ"});
 3173}
 3174
 3175#[gpui::test]
 3176fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3177    init_test(cx, |_| {});
 3178
 3179    let editor = cx.add_window(|window, cx| {
 3180        let buffer = MultiBuffer::build_simple(
 3181            "
 3182                a
 3183                b(
 3184                    X
 3185                )
 3186                c(
 3187                    X
 3188                )
 3189            "
 3190            .unindent()
 3191            .as_str(),
 3192            cx,
 3193        );
 3194        let mut editor = build_editor(buffer, window, cx);
 3195        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3196            s.select_ranges([
 3197                Point::new(2, 4)..Point::new(2, 5),
 3198                Point::new(5, 4)..Point::new(5, 5),
 3199            ])
 3200        });
 3201        editor
 3202    });
 3203
 3204    _ = editor.update(cx, |editor, window, cx| {
 3205        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3206        editor.buffer.update(cx, |buffer, cx| {
 3207            buffer.edit(
 3208                [
 3209                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3210                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3211                ],
 3212                None,
 3213                cx,
 3214            );
 3215            assert_eq!(
 3216                buffer.read(cx).text(),
 3217                "
 3218                    a
 3219                    b()
 3220                    c()
 3221                "
 3222                .unindent()
 3223            );
 3224        });
 3225        assert_eq!(
 3226            editor.selections.ranges(&editor.display_snapshot(cx)),
 3227            &[
 3228                Point::new(1, 2)..Point::new(1, 2),
 3229                Point::new(2, 2)..Point::new(2, 2),
 3230            ],
 3231        );
 3232
 3233        editor.newline(&Newline, window, cx);
 3234        assert_eq!(
 3235            editor.text(cx),
 3236            "
 3237                a
 3238                b(
 3239                )
 3240                c(
 3241                )
 3242            "
 3243            .unindent()
 3244        );
 3245
 3246        // The selections are moved after the inserted newlines
 3247        assert_eq!(
 3248            editor.selections.ranges(&editor.display_snapshot(cx)),
 3249            &[
 3250                Point::new(2, 0)..Point::new(2, 0),
 3251                Point::new(4, 0)..Point::new(4, 0),
 3252            ],
 3253        );
 3254    });
 3255}
 3256
 3257#[gpui::test]
 3258async fn test_newline_above(cx: &mut TestAppContext) {
 3259    init_test(cx, |settings| {
 3260        settings.defaults.tab_size = NonZeroU32::new(4)
 3261    });
 3262
 3263    let language = Arc::new(
 3264        Language::new(
 3265            LanguageConfig::default(),
 3266            Some(tree_sitter_rust::LANGUAGE.into()),
 3267        )
 3268        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3269        .unwrap(),
 3270    );
 3271
 3272    let mut cx = EditorTestContext::new(cx).await;
 3273    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3274    cx.set_state(indoc! {"
 3275        const a: ˇA = (
 3276 3277                «const_functionˇ»(ˇ),
 3278                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3279 3280        ˇ);ˇ
 3281    "});
 3282
 3283    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3284    cx.assert_editor_state(indoc! {"
 3285        ˇ
 3286        const a: A = (
 3287            ˇ
 3288            (
 3289                ˇ
 3290                ˇ
 3291                const_function(),
 3292                ˇ
 3293                ˇ
 3294                ˇ
 3295                ˇ
 3296                something_else,
 3297                ˇ
 3298            )
 3299            ˇ
 3300            ˇ
 3301        );
 3302    "});
 3303}
 3304
 3305#[gpui::test]
 3306async fn test_newline_below(cx: &mut TestAppContext) {
 3307    init_test(cx, |settings| {
 3308        settings.defaults.tab_size = NonZeroU32::new(4)
 3309    });
 3310
 3311    let language = Arc::new(
 3312        Language::new(
 3313            LanguageConfig::default(),
 3314            Some(tree_sitter_rust::LANGUAGE.into()),
 3315        )
 3316        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3317        .unwrap(),
 3318    );
 3319
 3320    let mut cx = EditorTestContext::new(cx).await;
 3321    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3322    cx.set_state(indoc! {"
 3323        const a: ˇA = (
 3324 3325                «const_functionˇ»(ˇ),
 3326                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3327 3328        ˇ);ˇ
 3329    "});
 3330
 3331    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3332    cx.assert_editor_state(indoc! {"
 3333        const a: A = (
 3334            ˇ
 3335            (
 3336                ˇ
 3337                const_function(),
 3338                ˇ
 3339                ˇ
 3340                something_else,
 3341                ˇ
 3342                ˇ
 3343                ˇ
 3344                ˇ
 3345            )
 3346            ˇ
 3347        );
 3348        ˇ
 3349        ˇ
 3350    "});
 3351}
 3352
 3353#[gpui::test]
 3354async fn test_newline_comments(cx: &mut TestAppContext) {
 3355    init_test(cx, |settings| {
 3356        settings.defaults.tab_size = NonZeroU32::new(4)
 3357    });
 3358
 3359    let language = Arc::new(Language::new(
 3360        LanguageConfig {
 3361            line_comments: vec!["// ".into()],
 3362            ..LanguageConfig::default()
 3363        },
 3364        None,
 3365    ));
 3366    {
 3367        let mut cx = EditorTestContext::new(cx).await;
 3368        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3369        cx.set_state(indoc! {"
 3370        // Fooˇ
 3371    "});
 3372
 3373        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3374        cx.assert_editor_state(indoc! {"
 3375        // Foo
 3376        // ˇ
 3377    "});
 3378        // Ensure that we add comment prefix when existing line contains space
 3379        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3380        cx.assert_editor_state(
 3381            indoc! {"
 3382        // Foo
 3383        //s
 3384        // ˇ
 3385    "}
 3386            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3387            .as_str(),
 3388        );
 3389        // Ensure that we add comment prefix when existing line does not contain space
 3390        cx.set_state(indoc! {"
 3391        // Foo
 3392        //ˇ
 3393    "});
 3394        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3395        cx.assert_editor_state(indoc! {"
 3396        // Foo
 3397        //
 3398        // ˇ
 3399    "});
 3400        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3401        cx.set_state(indoc! {"
 3402        ˇ// Foo
 3403    "});
 3404        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3405        cx.assert_editor_state(indoc! {"
 3406
 3407        ˇ// Foo
 3408    "});
 3409    }
 3410    // Ensure that comment continuations can be disabled.
 3411    update_test_language_settings(cx, |settings| {
 3412        settings.defaults.extend_comment_on_newline = Some(false);
 3413    });
 3414    let mut cx = EditorTestContext::new(cx).await;
 3415    cx.set_state(indoc! {"
 3416        // Fooˇ
 3417    "});
 3418    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3419    cx.assert_editor_state(indoc! {"
 3420        // Foo
 3421        ˇ
 3422    "});
 3423}
 3424
 3425#[gpui::test]
 3426async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3427    init_test(cx, |settings| {
 3428        settings.defaults.tab_size = NonZeroU32::new(4)
 3429    });
 3430
 3431    let language = Arc::new(Language::new(
 3432        LanguageConfig {
 3433            line_comments: vec!["// ".into(), "/// ".into()],
 3434            ..LanguageConfig::default()
 3435        },
 3436        None,
 3437    ));
 3438    {
 3439        let mut cx = EditorTestContext::new(cx).await;
 3440        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3441        cx.set_state(indoc! {"
 3442        //ˇ
 3443    "});
 3444        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3445        cx.assert_editor_state(indoc! {"
 3446        //
 3447        // ˇ
 3448    "});
 3449
 3450        cx.set_state(indoc! {"
 3451        ///ˇ
 3452    "});
 3453        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3454        cx.assert_editor_state(indoc! {"
 3455        ///
 3456        /// ˇ
 3457    "});
 3458    }
 3459}
 3460
 3461#[gpui::test]
 3462async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3463    init_test(cx, |settings| {
 3464        settings.defaults.tab_size = NonZeroU32::new(4)
 3465    });
 3466
 3467    let language = Arc::new(
 3468        Language::new(
 3469            LanguageConfig {
 3470                documentation_comment: Some(language::BlockCommentConfig {
 3471                    start: "/**".into(),
 3472                    end: "*/".into(),
 3473                    prefix: "* ".into(),
 3474                    tab_size: 1,
 3475                }),
 3476
 3477                ..LanguageConfig::default()
 3478            },
 3479            Some(tree_sitter_rust::LANGUAGE.into()),
 3480        )
 3481        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3482        .unwrap(),
 3483    );
 3484
 3485    {
 3486        let mut cx = EditorTestContext::new(cx).await;
 3487        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3488        cx.set_state(indoc! {"
 3489        /**ˇ
 3490    "});
 3491
 3492        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3493        cx.assert_editor_state(indoc! {"
 3494        /**
 3495         * ˇ
 3496    "});
 3497        // Ensure that if cursor is before the comment start,
 3498        // we do not actually insert a comment prefix.
 3499        cx.set_state(indoc! {"
 3500        ˇ/**
 3501    "});
 3502        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3503        cx.assert_editor_state(indoc! {"
 3504
 3505        ˇ/**
 3506    "});
 3507        // Ensure that if cursor is between it doesn't add comment prefix.
 3508        cx.set_state(indoc! {"
 3509        /*ˇ*
 3510    "});
 3511        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3512        cx.assert_editor_state(indoc! {"
 3513        /*
 3514        ˇ*
 3515    "});
 3516        // Ensure that if suffix exists on same line after cursor it adds new line.
 3517        cx.set_state(indoc! {"
 3518        /**ˇ*/
 3519    "});
 3520        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3521        cx.assert_editor_state(indoc! {"
 3522        /**
 3523         * ˇ
 3524         */
 3525    "});
 3526        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3527        cx.set_state(indoc! {"
 3528        /**ˇ */
 3529    "});
 3530        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3531        cx.assert_editor_state(indoc! {"
 3532        /**
 3533         * ˇ
 3534         */
 3535    "});
 3536        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3537        cx.set_state(indoc! {"
 3538        /** ˇ*/
 3539    "});
 3540        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3541        cx.assert_editor_state(
 3542            indoc! {"
 3543        /**s
 3544         * ˇ
 3545         */
 3546    "}
 3547            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3548            .as_str(),
 3549        );
 3550        // Ensure that delimiter space is preserved when newline on already
 3551        // spaced delimiter.
 3552        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3553        cx.assert_editor_state(
 3554            indoc! {"
 3555        /**s
 3556         *s
 3557         * ˇ
 3558         */
 3559    "}
 3560            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3561            .as_str(),
 3562        );
 3563        // Ensure that delimiter space is preserved when space is not
 3564        // on existing delimiter.
 3565        cx.set_state(indoc! {"
 3566        /**
 3567 3568         */
 3569    "});
 3570        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3571        cx.assert_editor_state(indoc! {"
 3572        /**
 3573         *
 3574         * ˇ
 3575         */
 3576    "});
 3577        // Ensure that if suffix exists on same line after cursor it
 3578        // doesn't add extra new line if prefix is not on same line.
 3579        cx.set_state(indoc! {"
 3580        /**
 3581        ˇ*/
 3582    "});
 3583        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3584        cx.assert_editor_state(indoc! {"
 3585        /**
 3586
 3587        ˇ*/
 3588    "});
 3589        // Ensure that it detects suffix after existing prefix.
 3590        cx.set_state(indoc! {"
 3591        /**ˇ/
 3592    "});
 3593        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3594        cx.assert_editor_state(indoc! {"
 3595        /**
 3596        ˇ/
 3597    "});
 3598        // Ensure that if suffix exists on same line before
 3599        // cursor it does not add comment prefix.
 3600        cx.set_state(indoc! {"
 3601        /** */ˇ
 3602    "});
 3603        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3604        cx.assert_editor_state(indoc! {"
 3605        /** */
 3606        ˇ
 3607    "});
 3608        // Ensure that if suffix exists on same line before
 3609        // cursor it does not add comment prefix.
 3610        cx.set_state(indoc! {"
 3611        /**
 3612         *
 3613         */ˇ
 3614    "});
 3615        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3616        cx.assert_editor_state(indoc! {"
 3617        /**
 3618         *
 3619         */
 3620         ˇ
 3621    "});
 3622
 3623        // Ensure that inline comment followed by code
 3624        // doesn't 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 text after comment end tag
 3635        // doesn't add comment prefix on newline
 3636        cx.set_state(indoc! {"
 3637        /**
 3638         *
 3639         */ˇtext
 3640    "});
 3641        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3642        cx.assert_editor_state(indoc! {"
 3643        /**
 3644         *
 3645         */
 3646         ˇtext
 3647    "});
 3648
 3649        // Ensure if not comment block it doesn't
 3650        // add comment prefix on newline
 3651        cx.set_state(indoc! {"
 3652        * textˇ
 3653    "});
 3654        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3655        cx.assert_editor_state(indoc! {"
 3656        * text
 3657        ˇ
 3658    "});
 3659    }
 3660    // Ensure that comment continuations can be disabled.
 3661    update_test_language_settings(cx, |settings| {
 3662        settings.defaults.extend_comment_on_newline = Some(false);
 3663    });
 3664    let mut cx = EditorTestContext::new(cx).await;
 3665    cx.set_state(indoc! {"
 3666        /**ˇ
 3667    "});
 3668    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3669    cx.assert_editor_state(indoc! {"
 3670        /**
 3671        ˇ
 3672    "});
 3673}
 3674
 3675#[gpui::test]
 3676async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3677    init_test(cx, |settings| {
 3678        settings.defaults.tab_size = NonZeroU32::new(4)
 3679    });
 3680
 3681    let lua_language = Arc::new(Language::new(
 3682        LanguageConfig {
 3683            line_comments: vec!["--".into()],
 3684            block_comment: Some(language::BlockCommentConfig {
 3685                start: "--[[".into(),
 3686                prefix: "".into(),
 3687                end: "]]".into(),
 3688                tab_size: 0,
 3689            }),
 3690            ..LanguageConfig::default()
 3691        },
 3692        None,
 3693    ));
 3694
 3695    let mut cx = EditorTestContext::new(cx).await;
 3696    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3697
 3698    // Line with line comment should extend
 3699    cx.set_state(indoc! {"
 3700        --ˇ
 3701    "});
 3702    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3703    cx.assert_editor_state(indoc! {"
 3704        --
 3705        --ˇ
 3706    "});
 3707
 3708    // Line with block comment that matches line comment should not extend
 3709    cx.set_state(indoc! {"
 3710        --[[ˇ
 3711    "});
 3712    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3713    cx.assert_editor_state(indoc! {"
 3714        --[[
 3715        ˇ
 3716    "});
 3717}
 3718
 3719#[gpui::test]
 3720fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3721    init_test(cx, |_| {});
 3722
 3723    let editor = cx.add_window(|window, cx| {
 3724        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3725        let mut editor = build_editor(buffer, window, cx);
 3726        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3727            s.select_ranges([
 3728                MultiBufferOffset(3)..MultiBufferOffset(4),
 3729                MultiBufferOffset(11)..MultiBufferOffset(12),
 3730                MultiBufferOffset(19)..MultiBufferOffset(20),
 3731            ])
 3732        });
 3733        editor
 3734    });
 3735
 3736    _ = editor.update(cx, |editor, window, cx| {
 3737        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3738        editor.buffer.update(cx, |buffer, cx| {
 3739            buffer.edit(
 3740                [
 3741                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 3742                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 3743                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 3744                ],
 3745                None,
 3746                cx,
 3747            );
 3748            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3749        });
 3750        assert_eq!(
 3751            editor.selections.ranges(&editor.display_snapshot(cx)),
 3752            &[
 3753                MultiBufferOffset(2)..MultiBufferOffset(2),
 3754                MultiBufferOffset(7)..MultiBufferOffset(7),
 3755                MultiBufferOffset(12)..MultiBufferOffset(12)
 3756            ],
 3757        );
 3758
 3759        editor.insert("Z", window, cx);
 3760        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3761
 3762        // The selections are moved after the inserted characters
 3763        assert_eq!(
 3764            editor.selections.ranges(&editor.display_snapshot(cx)),
 3765            &[
 3766                MultiBufferOffset(3)..MultiBufferOffset(3),
 3767                MultiBufferOffset(9)..MultiBufferOffset(9),
 3768                MultiBufferOffset(15)..MultiBufferOffset(15)
 3769            ],
 3770        );
 3771    });
 3772}
 3773
 3774#[gpui::test]
 3775async fn test_tab(cx: &mut TestAppContext) {
 3776    init_test(cx, |settings| {
 3777        settings.defaults.tab_size = NonZeroU32::new(3)
 3778    });
 3779
 3780    let mut cx = EditorTestContext::new(cx).await;
 3781    cx.set_state(indoc! {"
 3782        ˇabˇc
 3783        ˇ🏀ˇ🏀ˇefg
 3784 3785    "});
 3786    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3787    cx.assert_editor_state(indoc! {"
 3788           ˇab ˇc
 3789           ˇ🏀  ˇ🏀  ˇefg
 3790        d  ˇ
 3791    "});
 3792
 3793    cx.set_state(indoc! {"
 3794        a
 3795        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3796    "});
 3797    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3798    cx.assert_editor_state(indoc! {"
 3799        a
 3800           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3801    "});
 3802}
 3803
 3804#[gpui::test]
 3805async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3806    init_test(cx, |_| {});
 3807
 3808    let mut cx = EditorTestContext::new(cx).await;
 3809    let language = Arc::new(
 3810        Language::new(
 3811            LanguageConfig::default(),
 3812            Some(tree_sitter_rust::LANGUAGE.into()),
 3813        )
 3814        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3815        .unwrap(),
 3816    );
 3817    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3818
 3819    // test when all cursors are not at suggested indent
 3820    // then simply move to their suggested indent location
 3821    cx.set_state(indoc! {"
 3822        const a: B = (
 3823            c(
 3824        ˇ
 3825        ˇ    )
 3826        );
 3827    "});
 3828    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3829    cx.assert_editor_state(indoc! {"
 3830        const a: B = (
 3831            c(
 3832                ˇ
 3833            ˇ)
 3834        );
 3835    "});
 3836
 3837    // test cursor already at suggested indent not moving when
 3838    // other cursors are yet to reach their suggested indents
 3839    cx.set_state(indoc! {"
 3840        ˇ
 3841        const a: B = (
 3842            c(
 3843                d(
 3844        ˇ
 3845                )
 3846        ˇ
 3847        ˇ    )
 3848        );
 3849    "});
 3850    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3851    cx.assert_editor_state(indoc! {"
 3852        ˇ
 3853        const a: B = (
 3854            c(
 3855                d(
 3856                    ˇ
 3857                )
 3858                ˇ
 3859            ˇ)
 3860        );
 3861    "});
 3862    // test when all cursors are at suggested indent then tab is inserted
 3863    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3864    cx.assert_editor_state(indoc! {"
 3865            ˇ
 3866        const a: B = (
 3867            c(
 3868                d(
 3869                        ˇ
 3870                )
 3871                    ˇ
 3872                ˇ)
 3873        );
 3874    "});
 3875
 3876    // test when current indent is less than suggested indent,
 3877    // we adjust line to match suggested indent and move cursor to it
 3878    //
 3879    // when no other cursor is at word boundary, all of them should move
 3880    cx.set_state(indoc! {"
 3881        const a: B = (
 3882            c(
 3883                d(
 3884        ˇ
 3885        ˇ   )
 3886        ˇ   )
 3887        );
 3888    "});
 3889    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3890    cx.assert_editor_state(indoc! {"
 3891        const a: B = (
 3892            c(
 3893                d(
 3894                    ˇ
 3895                ˇ)
 3896            ˇ)
 3897        );
 3898    "});
 3899
 3900    // test when current indent is less than suggested indent,
 3901    // we adjust line to match suggested indent and move cursor to it
 3902    //
 3903    // when some other cursor is at word boundary, it should not move
 3904    cx.set_state(indoc! {"
 3905        const a: B = (
 3906            c(
 3907                d(
 3908        ˇ
 3909        ˇ   )
 3910           ˇ)
 3911        );
 3912    "});
 3913    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3914    cx.assert_editor_state(indoc! {"
 3915        const a: B = (
 3916            c(
 3917                d(
 3918                    ˇ
 3919                ˇ)
 3920            ˇ)
 3921        );
 3922    "});
 3923
 3924    // test when current indent is more than suggested indent,
 3925    // we just move cursor to current indent instead of suggested indent
 3926    //
 3927    // when no other cursor is at word boundary, all of them should move
 3928    cx.set_state(indoc! {"
 3929        const a: B = (
 3930            c(
 3931                d(
 3932        ˇ
 3933        ˇ                )
 3934        ˇ   )
 3935        );
 3936    "});
 3937    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3938    cx.assert_editor_state(indoc! {"
 3939        const a: B = (
 3940            c(
 3941                d(
 3942                    ˇ
 3943                        ˇ)
 3944            ˇ)
 3945        );
 3946    "});
 3947    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3948    cx.assert_editor_state(indoc! {"
 3949        const a: B = (
 3950            c(
 3951                d(
 3952                        ˇ
 3953                            ˇ)
 3954                ˇ)
 3955        );
 3956    "});
 3957
 3958    // test when current indent is more than suggested indent,
 3959    // we just move cursor to current indent instead of suggested indent
 3960    //
 3961    // when some other cursor is at word boundary, it doesn't move
 3962    cx.set_state(indoc! {"
 3963        const a: B = (
 3964            c(
 3965                d(
 3966        ˇ
 3967        ˇ                )
 3968            ˇ)
 3969        );
 3970    "});
 3971    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3972    cx.assert_editor_state(indoc! {"
 3973        const a: B = (
 3974            c(
 3975                d(
 3976                    ˇ
 3977                        ˇ)
 3978            ˇ)
 3979        );
 3980    "});
 3981
 3982    // handle auto-indent when there are multiple cursors on the same line
 3983    cx.set_state(indoc! {"
 3984        const a: B = (
 3985            c(
 3986        ˇ    ˇ
 3987        ˇ    )
 3988        );
 3989    "});
 3990    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3991    cx.assert_editor_state(indoc! {"
 3992        const a: B = (
 3993            c(
 3994                ˇ
 3995            ˇ)
 3996        );
 3997    "});
 3998}
 3999
 4000#[gpui::test]
 4001async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4002    init_test(cx, |settings| {
 4003        settings.defaults.tab_size = NonZeroU32::new(3)
 4004    });
 4005
 4006    let mut cx = EditorTestContext::new(cx).await;
 4007    cx.set_state(indoc! {"
 4008         ˇ
 4009        \t ˇ
 4010        \t  ˇ
 4011        \t   ˇ
 4012         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4013    "});
 4014
 4015    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4016    cx.assert_editor_state(indoc! {"
 4017           ˇ
 4018        \t   ˇ
 4019        \t   ˇ
 4020        \t      ˇ
 4021         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4022    "});
 4023}
 4024
 4025#[gpui::test]
 4026async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4027    init_test(cx, |settings| {
 4028        settings.defaults.tab_size = NonZeroU32::new(4)
 4029    });
 4030
 4031    let language = Arc::new(
 4032        Language::new(
 4033            LanguageConfig::default(),
 4034            Some(tree_sitter_rust::LANGUAGE.into()),
 4035        )
 4036        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4037        .unwrap(),
 4038    );
 4039
 4040    let mut cx = EditorTestContext::new(cx).await;
 4041    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4042    cx.set_state(indoc! {"
 4043        fn a() {
 4044            if b {
 4045        \t ˇc
 4046            }
 4047        }
 4048    "});
 4049
 4050    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4051    cx.assert_editor_state(indoc! {"
 4052        fn a() {
 4053            if b {
 4054                ˇc
 4055            }
 4056        }
 4057    "});
 4058}
 4059
 4060#[gpui::test]
 4061async fn test_indent_outdent(cx: &mut TestAppContext) {
 4062    init_test(cx, |settings| {
 4063        settings.defaults.tab_size = NonZeroU32::new(4);
 4064    });
 4065
 4066    let mut cx = EditorTestContext::new(cx).await;
 4067
 4068    cx.set_state(indoc! {"
 4069          «oneˇ» «twoˇ»
 4070        three
 4071         four
 4072    "});
 4073    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4074    cx.assert_editor_state(indoc! {"
 4075            «oneˇ» «twoˇ»
 4076        three
 4077         four
 4078    "});
 4079
 4080    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4081    cx.assert_editor_state(indoc! {"
 4082        «oneˇ» «twoˇ»
 4083        three
 4084         four
 4085    "});
 4086
 4087    // select across line ending
 4088    cx.set_state(indoc! {"
 4089        one two
 4090        t«hree
 4091        ˇ» four
 4092    "});
 4093    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4094    cx.assert_editor_state(indoc! {"
 4095        one two
 4096            t«hree
 4097        ˇ» four
 4098    "});
 4099
 4100    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4101    cx.assert_editor_state(indoc! {"
 4102        one two
 4103        t«hree
 4104        ˇ» four
 4105    "});
 4106
 4107    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4108    cx.set_state(indoc! {"
 4109        one two
 4110        ˇthree
 4111            four
 4112    "});
 4113    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4114    cx.assert_editor_state(indoc! {"
 4115        one two
 4116            ˇthree
 4117            four
 4118    "});
 4119
 4120    cx.set_state(indoc! {"
 4121        one two
 4122        ˇ    three
 4123            four
 4124    "});
 4125    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4126    cx.assert_editor_state(indoc! {"
 4127        one two
 4128        ˇthree
 4129            four
 4130    "});
 4131}
 4132
 4133#[gpui::test]
 4134async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4135    // This is a regression test for issue #33761
 4136    init_test(cx, |_| {});
 4137
 4138    let mut cx = EditorTestContext::new(cx).await;
 4139    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4140    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4141
 4142    cx.set_state(
 4143        r#"ˇ#     ingress:
 4144ˇ#         api:
 4145ˇ#             enabled: false
 4146ˇ#             pathType: Prefix
 4147ˇ#           console:
 4148ˇ#               enabled: false
 4149ˇ#               pathType: Prefix
 4150"#,
 4151    );
 4152
 4153    // Press tab to indent all lines
 4154    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4155
 4156    cx.assert_editor_state(
 4157        r#"    ˇ#     ingress:
 4158    ˇ#         api:
 4159    ˇ#             enabled: false
 4160    ˇ#             pathType: Prefix
 4161    ˇ#           console:
 4162    ˇ#               enabled: false
 4163    ˇ#               pathType: Prefix
 4164"#,
 4165    );
 4166}
 4167
 4168#[gpui::test]
 4169async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4170    // This is a test to make sure our fix for issue #33761 didn't break anything
 4171    init_test(cx, |_| {});
 4172
 4173    let mut cx = EditorTestContext::new(cx).await;
 4174    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4175    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4176
 4177    cx.set_state(
 4178        r#"ˇingress:
 4179ˇ  api:
 4180ˇ    enabled: false
 4181ˇ    pathType: Prefix
 4182"#,
 4183    );
 4184
 4185    // Press tab to indent all lines
 4186    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4187
 4188    cx.assert_editor_state(
 4189        r#"ˇingress:
 4190    ˇapi:
 4191        ˇenabled: false
 4192        ˇpathType: Prefix
 4193"#,
 4194    );
 4195}
 4196
 4197#[gpui::test]
 4198async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4199    init_test(cx, |settings| {
 4200        settings.defaults.hard_tabs = Some(true);
 4201    });
 4202
 4203    let mut cx = EditorTestContext::new(cx).await;
 4204
 4205    // select two ranges on one line
 4206    cx.set_state(indoc! {"
 4207        «oneˇ» «twoˇ»
 4208        three
 4209        four
 4210    "});
 4211    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4212    cx.assert_editor_state(indoc! {"
 4213        \t«oneˇ» «twoˇ»
 4214        three
 4215        four
 4216    "});
 4217    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4218    cx.assert_editor_state(indoc! {"
 4219        \t\t«oneˇ» «twoˇ»
 4220        three
 4221        four
 4222    "});
 4223    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4224    cx.assert_editor_state(indoc! {"
 4225        \t«oneˇ» «twoˇ»
 4226        three
 4227        four
 4228    "});
 4229    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4230    cx.assert_editor_state(indoc! {"
 4231        «oneˇ» «twoˇ»
 4232        three
 4233        four
 4234    "});
 4235
 4236    // select across a line ending
 4237    cx.set_state(indoc! {"
 4238        one two
 4239        t«hree
 4240        ˇ»four
 4241    "});
 4242    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4243    cx.assert_editor_state(indoc! {"
 4244        one two
 4245        \tt«hree
 4246        ˇ»four
 4247    "});
 4248    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4249    cx.assert_editor_state(indoc! {"
 4250        one two
 4251        \t\tt«hree
 4252        ˇ»four
 4253    "});
 4254    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4255    cx.assert_editor_state(indoc! {"
 4256        one two
 4257        \tt«hree
 4258        ˇ»four
 4259    "});
 4260    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4261    cx.assert_editor_state(indoc! {"
 4262        one two
 4263        t«hree
 4264        ˇ»four
 4265    "});
 4266
 4267    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4268    cx.set_state(indoc! {"
 4269        one two
 4270        ˇthree
 4271        four
 4272    "});
 4273    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4274    cx.assert_editor_state(indoc! {"
 4275        one two
 4276        ˇthree
 4277        four
 4278    "});
 4279    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4280    cx.assert_editor_state(indoc! {"
 4281        one two
 4282        \tˇthree
 4283        four
 4284    "});
 4285    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4286    cx.assert_editor_state(indoc! {"
 4287        one two
 4288        ˇthree
 4289        four
 4290    "});
 4291}
 4292
 4293#[gpui::test]
 4294fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4295    init_test(cx, |settings| {
 4296        settings.languages.0.extend([
 4297            (
 4298                "TOML".into(),
 4299                LanguageSettingsContent {
 4300                    tab_size: NonZeroU32::new(2),
 4301                    ..Default::default()
 4302                },
 4303            ),
 4304            (
 4305                "Rust".into(),
 4306                LanguageSettingsContent {
 4307                    tab_size: NonZeroU32::new(4),
 4308                    ..Default::default()
 4309                },
 4310            ),
 4311        ]);
 4312    });
 4313
 4314    let toml_language = Arc::new(Language::new(
 4315        LanguageConfig {
 4316            name: "TOML".into(),
 4317            ..Default::default()
 4318        },
 4319        None,
 4320    ));
 4321    let rust_language = Arc::new(Language::new(
 4322        LanguageConfig {
 4323            name: "Rust".into(),
 4324            ..Default::default()
 4325        },
 4326        None,
 4327    ));
 4328
 4329    let toml_buffer =
 4330        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4331    let rust_buffer =
 4332        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4333    let multibuffer = cx.new(|cx| {
 4334        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4335        multibuffer.push_excerpts(
 4336            toml_buffer.clone(),
 4337            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4338            cx,
 4339        );
 4340        multibuffer.push_excerpts(
 4341            rust_buffer.clone(),
 4342            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4343            cx,
 4344        );
 4345        multibuffer
 4346    });
 4347
 4348    cx.add_window(|window, cx| {
 4349        let mut editor = build_editor(multibuffer, window, cx);
 4350
 4351        assert_eq!(
 4352            editor.text(cx),
 4353            indoc! {"
 4354                a = 1
 4355                b = 2
 4356
 4357                const c: usize = 3;
 4358            "}
 4359        );
 4360
 4361        select_ranges(
 4362            &mut editor,
 4363            indoc! {"
 4364                «aˇ» = 1
 4365                b = 2
 4366
 4367                «const c:ˇ» usize = 3;
 4368            "},
 4369            window,
 4370            cx,
 4371        );
 4372
 4373        editor.tab(&Tab, window, cx);
 4374        assert_text_with_selections(
 4375            &mut editor,
 4376            indoc! {"
 4377                  «aˇ» = 1
 4378                b = 2
 4379
 4380                    «const c:ˇ» usize = 3;
 4381            "},
 4382            cx,
 4383        );
 4384        editor.backtab(&Backtab, window, cx);
 4385        assert_text_with_selections(
 4386            &mut editor,
 4387            indoc! {"
 4388                «aˇ» = 1
 4389                b = 2
 4390
 4391                «const c:ˇ» usize = 3;
 4392            "},
 4393            cx,
 4394        );
 4395
 4396        editor
 4397    });
 4398}
 4399
 4400#[gpui::test]
 4401async fn test_backspace(cx: &mut TestAppContext) {
 4402    init_test(cx, |_| {});
 4403
 4404    let mut cx = EditorTestContext::new(cx).await;
 4405
 4406    // Basic backspace
 4407    cx.set_state(indoc! {"
 4408        onˇe two three
 4409        fou«rˇ» five six
 4410        seven «ˇeight nine
 4411        »ten
 4412    "});
 4413    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4414    cx.assert_editor_state(indoc! {"
 4415        oˇe two three
 4416        fouˇ five six
 4417        seven ˇten
 4418    "});
 4419
 4420    // Test backspace inside and around indents
 4421    cx.set_state(indoc! {"
 4422        zero
 4423            ˇone
 4424                ˇtwo
 4425            ˇ ˇ ˇ  three
 4426        ˇ  ˇ  four
 4427    "});
 4428    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4429    cx.assert_editor_state(indoc! {"
 4430        zero
 4431        ˇone
 4432            ˇtwo
 4433        ˇ  threeˇ  four
 4434    "});
 4435}
 4436
 4437#[gpui::test]
 4438async fn test_delete(cx: &mut TestAppContext) {
 4439    init_test(cx, |_| {});
 4440
 4441    let mut cx = EditorTestContext::new(cx).await;
 4442    cx.set_state(indoc! {"
 4443        onˇe two three
 4444        fou«rˇ» five six
 4445        seven «ˇeight nine
 4446        »ten
 4447    "});
 4448    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4449    cx.assert_editor_state(indoc! {"
 4450        onˇ two three
 4451        fouˇ five six
 4452        seven ˇten
 4453    "});
 4454}
 4455
 4456#[gpui::test]
 4457fn test_delete_line(cx: &mut TestAppContext) {
 4458    init_test(cx, |_| {});
 4459
 4460    let editor = cx.add_window(|window, cx| {
 4461        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4462        build_editor(buffer, window, cx)
 4463    });
 4464    _ = editor.update(cx, |editor, window, cx| {
 4465        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4466            s.select_display_ranges([
 4467                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4468                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4469                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4470            ])
 4471        });
 4472        editor.delete_line(&DeleteLine, window, cx);
 4473        assert_eq!(editor.display_text(cx), "ghi");
 4474        assert_eq!(
 4475            display_ranges(editor, cx),
 4476            vec![
 4477                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4478                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4479            ]
 4480        );
 4481    });
 4482
 4483    let editor = cx.add_window(|window, cx| {
 4484        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4485        build_editor(buffer, window, cx)
 4486    });
 4487    _ = editor.update(cx, |editor, window, cx| {
 4488        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4489            s.select_display_ranges([
 4490                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4491            ])
 4492        });
 4493        editor.delete_line(&DeleteLine, window, cx);
 4494        assert_eq!(editor.display_text(cx), "ghi\n");
 4495        assert_eq!(
 4496            display_ranges(editor, cx),
 4497            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4498        );
 4499    });
 4500
 4501    let editor = cx.add_window(|window, cx| {
 4502        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4503        build_editor(buffer, window, cx)
 4504    });
 4505    _ = editor.update(cx, |editor, window, cx| {
 4506        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4507            s.select_display_ranges([
 4508                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4509            ])
 4510        });
 4511        editor.delete_line(&DeleteLine, window, cx);
 4512        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4513        assert_eq!(
 4514            display_ranges(editor, cx),
 4515            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4516        );
 4517    });
 4518}
 4519
 4520#[gpui::test]
 4521fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4522    init_test(cx, |_| {});
 4523
 4524    cx.add_window(|window, cx| {
 4525        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4526        let mut editor = build_editor(buffer.clone(), window, cx);
 4527        let buffer = buffer.read(cx).as_singleton().unwrap();
 4528
 4529        assert_eq!(
 4530            editor
 4531                .selections
 4532                .ranges::<Point>(&editor.display_snapshot(cx)),
 4533            &[Point::new(0, 0)..Point::new(0, 0)]
 4534        );
 4535
 4536        // When on single line, replace newline at end by space
 4537        editor.join_lines(&JoinLines, window, cx);
 4538        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4539        assert_eq!(
 4540            editor
 4541                .selections
 4542                .ranges::<Point>(&editor.display_snapshot(cx)),
 4543            &[Point::new(0, 3)..Point::new(0, 3)]
 4544        );
 4545
 4546        // When multiple lines are selected, remove newlines that are spanned by the selection
 4547        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4548            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4549        });
 4550        editor.join_lines(&JoinLines, window, cx);
 4551        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4552        assert_eq!(
 4553            editor
 4554                .selections
 4555                .ranges::<Point>(&editor.display_snapshot(cx)),
 4556            &[Point::new(0, 11)..Point::new(0, 11)]
 4557        );
 4558
 4559        // Undo should be transactional
 4560        editor.undo(&Undo, window, cx);
 4561        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4562        assert_eq!(
 4563            editor
 4564                .selections
 4565                .ranges::<Point>(&editor.display_snapshot(cx)),
 4566            &[Point::new(0, 5)..Point::new(2, 2)]
 4567        );
 4568
 4569        // When joining an empty line don't insert a space
 4570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4571            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4572        });
 4573        editor.join_lines(&JoinLines, window, cx);
 4574        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4575        assert_eq!(
 4576            editor
 4577                .selections
 4578                .ranges::<Point>(&editor.display_snapshot(cx)),
 4579            [Point::new(2, 3)..Point::new(2, 3)]
 4580        );
 4581
 4582        // We can remove trailing newlines
 4583        editor.join_lines(&JoinLines, window, cx);
 4584        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4585        assert_eq!(
 4586            editor
 4587                .selections
 4588                .ranges::<Point>(&editor.display_snapshot(cx)),
 4589            [Point::new(2, 3)..Point::new(2, 3)]
 4590        );
 4591
 4592        // We don't blow up on the last line
 4593        editor.join_lines(&JoinLines, window, cx);
 4594        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4595        assert_eq!(
 4596            editor
 4597                .selections
 4598                .ranges::<Point>(&editor.display_snapshot(cx)),
 4599            [Point::new(2, 3)..Point::new(2, 3)]
 4600        );
 4601
 4602        // reset to test indentation
 4603        editor.buffer.update(cx, |buffer, cx| {
 4604            buffer.edit(
 4605                [
 4606                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4607                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4608                ],
 4609                None,
 4610                cx,
 4611            )
 4612        });
 4613
 4614        // We remove any leading spaces
 4615        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4616        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4617            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4618        });
 4619        editor.join_lines(&JoinLines, window, cx);
 4620        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4621
 4622        // We don't insert a space for a line containing only spaces
 4623        editor.join_lines(&JoinLines, window, cx);
 4624        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4625
 4626        // We ignore any leading tabs
 4627        editor.join_lines(&JoinLines, window, cx);
 4628        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4629
 4630        editor
 4631    });
 4632}
 4633
 4634#[gpui::test]
 4635fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4636    init_test(cx, |_| {});
 4637
 4638    cx.add_window(|window, cx| {
 4639        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4640        let mut editor = build_editor(buffer.clone(), window, cx);
 4641        let buffer = buffer.read(cx).as_singleton().unwrap();
 4642
 4643        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4644            s.select_ranges([
 4645                Point::new(0, 2)..Point::new(1, 1),
 4646                Point::new(1, 2)..Point::new(1, 2),
 4647                Point::new(3, 1)..Point::new(3, 2),
 4648            ])
 4649        });
 4650
 4651        editor.join_lines(&JoinLines, window, cx);
 4652        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4653
 4654        assert_eq!(
 4655            editor
 4656                .selections
 4657                .ranges::<Point>(&editor.display_snapshot(cx)),
 4658            [
 4659                Point::new(0, 7)..Point::new(0, 7),
 4660                Point::new(1, 3)..Point::new(1, 3)
 4661            ]
 4662        );
 4663        editor
 4664    });
 4665}
 4666
 4667#[gpui::test]
 4668async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4669    init_test(cx, |_| {});
 4670
 4671    let mut cx = EditorTestContext::new(cx).await;
 4672
 4673    let diff_base = r#"
 4674        Line 0
 4675        Line 1
 4676        Line 2
 4677        Line 3
 4678        "#
 4679    .unindent();
 4680
 4681    cx.set_state(
 4682        &r#"
 4683        ˇLine 0
 4684        Line 1
 4685        Line 2
 4686        Line 3
 4687        "#
 4688        .unindent(),
 4689    );
 4690
 4691    cx.set_head_text(&diff_base);
 4692    executor.run_until_parked();
 4693
 4694    // Join lines
 4695    cx.update_editor(|editor, window, cx| {
 4696        editor.join_lines(&JoinLines, window, cx);
 4697    });
 4698    executor.run_until_parked();
 4699
 4700    cx.assert_editor_state(
 4701        &r#"
 4702        Line 0ˇ Line 1
 4703        Line 2
 4704        Line 3
 4705        "#
 4706        .unindent(),
 4707    );
 4708    // Join again
 4709    cx.update_editor(|editor, window, cx| {
 4710        editor.join_lines(&JoinLines, window, cx);
 4711    });
 4712    executor.run_until_parked();
 4713
 4714    cx.assert_editor_state(
 4715        &r#"
 4716        Line 0 Line 1ˇ Line 2
 4717        Line 3
 4718        "#
 4719        .unindent(),
 4720    );
 4721}
 4722
 4723#[gpui::test]
 4724async fn test_custom_newlines_cause_no_false_positive_diffs(
 4725    executor: BackgroundExecutor,
 4726    cx: &mut TestAppContext,
 4727) {
 4728    init_test(cx, |_| {});
 4729    let mut cx = EditorTestContext::new(cx).await;
 4730    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4731    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4732    executor.run_until_parked();
 4733
 4734    cx.update_editor(|editor, window, cx| {
 4735        let snapshot = editor.snapshot(window, cx);
 4736        assert_eq!(
 4737            snapshot
 4738                .buffer_snapshot()
 4739                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 4740                .collect::<Vec<_>>(),
 4741            Vec::new(),
 4742            "Should not have any diffs for files with custom newlines"
 4743        );
 4744    });
 4745}
 4746
 4747#[gpui::test]
 4748async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4749    init_test(cx, |_| {});
 4750
 4751    let mut cx = EditorTestContext::new(cx).await;
 4752
 4753    // Test sort_lines_case_insensitive()
 4754    cx.set_state(indoc! {"
 4755        «z
 4756        y
 4757        x
 4758        Z
 4759        Y
 4760        Xˇ»
 4761    "});
 4762    cx.update_editor(|e, window, cx| {
 4763        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4764    });
 4765    cx.assert_editor_state(indoc! {"
 4766        «x
 4767        X
 4768        y
 4769        Y
 4770        z
 4771        Zˇ»
 4772    "});
 4773
 4774    // Test sort_lines_by_length()
 4775    //
 4776    // Demonstrates:
 4777    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4778    // - sort is stable
 4779    cx.set_state(indoc! {"
 4780        «123
 4781        æ
 4782        12
 4783 4784        1
 4785        æˇ»
 4786    "});
 4787    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4788    cx.assert_editor_state(indoc! {"
 4789        «æ
 4790 4791        1
 4792        æ
 4793        12
 4794        123ˇ»
 4795    "});
 4796
 4797    // Test reverse_lines()
 4798    cx.set_state(indoc! {"
 4799        «5
 4800        4
 4801        3
 4802        2
 4803        1ˇ»
 4804    "});
 4805    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4806    cx.assert_editor_state(indoc! {"
 4807        «1
 4808        2
 4809        3
 4810        4
 4811        5ˇ»
 4812    "});
 4813
 4814    // Skip testing shuffle_line()
 4815
 4816    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4817    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4818
 4819    // Don't manipulate when cursor is on single line, but expand the selection
 4820    cx.set_state(indoc! {"
 4821        ddˇdd
 4822        ccc
 4823        bb
 4824        a
 4825    "});
 4826    cx.update_editor(|e, window, cx| {
 4827        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4828    });
 4829    cx.assert_editor_state(indoc! {"
 4830        «ddddˇ»
 4831        ccc
 4832        bb
 4833        a
 4834    "});
 4835
 4836    // Basic manipulate case
 4837    // Start selection moves to column 0
 4838    // End of selection shrinks to fit shorter line
 4839    cx.set_state(indoc! {"
 4840        dd«d
 4841        ccc
 4842        bb
 4843        aaaaaˇ»
 4844    "});
 4845    cx.update_editor(|e, window, cx| {
 4846        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4847    });
 4848    cx.assert_editor_state(indoc! {"
 4849        «aaaaa
 4850        bb
 4851        ccc
 4852        dddˇ»
 4853    "});
 4854
 4855    // Manipulate case with newlines
 4856    cx.set_state(indoc! {"
 4857        dd«d
 4858        ccc
 4859
 4860        bb
 4861        aaaaa
 4862
 4863        ˇ»
 4864    "});
 4865    cx.update_editor(|e, window, cx| {
 4866        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4867    });
 4868    cx.assert_editor_state(indoc! {"
 4869        «
 4870
 4871        aaaaa
 4872        bb
 4873        ccc
 4874        dddˇ»
 4875
 4876    "});
 4877
 4878    // Adding new line
 4879    cx.set_state(indoc! {"
 4880        aa«a
 4881        bbˇ»b
 4882    "});
 4883    cx.update_editor(|e, window, cx| {
 4884        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4885    });
 4886    cx.assert_editor_state(indoc! {"
 4887        «aaa
 4888        bbb
 4889        added_lineˇ»
 4890    "});
 4891
 4892    // Removing line
 4893    cx.set_state(indoc! {"
 4894        aa«a
 4895        bbbˇ»
 4896    "});
 4897    cx.update_editor(|e, window, cx| {
 4898        e.manipulate_immutable_lines(window, cx, |lines| {
 4899            lines.pop();
 4900        })
 4901    });
 4902    cx.assert_editor_state(indoc! {"
 4903        «aaaˇ»
 4904    "});
 4905
 4906    // Removing all lines
 4907    cx.set_state(indoc! {"
 4908        aa«a
 4909        bbbˇ»
 4910    "});
 4911    cx.update_editor(|e, window, cx| {
 4912        e.manipulate_immutable_lines(window, cx, |lines| {
 4913            lines.drain(..);
 4914        })
 4915    });
 4916    cx.assert_editor_state(indoc! {"
 4917        ˇ
 4918    "});
 4919}
 4920
 4921#[gpui::test]
 4922async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4923    init_test(cx, |_| {});
 4924
 4925    let mut cx = EditorTestContext::new(cx).await;
 4926
 4927    // Consider continuous selection as single selection
 4928    cx.set_state(indoc! {"
 4929        Aaa«aa
 4930        cˇ»c«c
 4931        bb
 4932        aaaˇ»aa
 4933    "});
 4934    cx.update_editor(|e, window, cx| {
 4935        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4936    });
 4937    cx.assert_editor_state(indoc! {"
 4938        «Aaaaa
 4939        ccc
 4940        bb
 4941        aaaaaˇ»
 4942    "});
 4943
 4944    cx.set_state(indoc! {"
 4945        Aaa«aa
 4946        cˇ»c«c
 4947        bb
 4948        aaaˇ»aa
 4949    "});
 4950    cx.update_editor(|e, window, cx| {
 4951        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4952    });
 4953    cx.assert_editor_state(indoc! {"
 4954        «Aaaaa
 4955        ccc
 4956        bbˇ»
 4957    "});
 4958
 4959    // Consider non continuous selection as distinct dedup operations
 4960    cx.set_state(indoc! {"
 4961        «aaaaa
 4962        bb
 4963        aaaaa
 4964        aaaaaˇ»
 4965
 4966        aaa«aaˇ»
 4967    "});
 4968    cx.update_editor(|e, window, cx| {
 4969        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4970    });
 4971    cx.assert_editor_state(indoc! {"
 4972        «aaaaa
 4973        bbˇ»
 4974
 4975        «aaaaaˇ»
 4976    "});
 4977}
 4978
 4979#[gpui::test]
 4980async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4981    init_test(cx, |_| {});
 4982
 4983    let mut cx = EditorTestContext::new(cx).await;
 4984
 4985    cx.set_state(indoc! {"
 4986        «Aaa
 4987        aAa
 4988        Aaaˇ»
 4989    "});
 4990    cx.update_editor(|e, window, cx| {
 4991        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4992    });
 4993    cx.assert_editor_state(indoc! {"
 4994        «Aaa
 4995        aAaˇ»
 4996    "});
 4997
 4998    cx.set_state(indoc! {"
 4999        «Aaa
 5000        aAa
 5001        aaAˇ»
 5002    "});
 5003    cx.update_editor(|e, window, cx| {
 5004        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5005    });
 5006    cx.assert_editor_state(indoc! {"
 5007        «Aaaˇ»
 5008    "});
 5009}
 5010
 5011#[gpui::test]
 5012async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5013    init_test(cx, |_| {});
 5014
 5015    let mut cx = EditorTestContext::new(cx).await;
 5016
 5017    let js_language = Arc::new(Language::new(
 5018        LanguageConfig {
 5019            name: "JavaScript".into(),
 5020            wrap_characters: Some(language::WrapCharactersConfig {
 5021                start_prefix: "<".into(),
 5022                start_suffix: ">".into(),
 5023                end_prefix: "</".into(),
 5024                end_suffix: ">".into(),
 5025            }),
 5026            ..LanguageConfig::default()
 5027        },
 5028        None,
 5029    ));
 5030
 5031    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5032
 5033    cx.set_state(indoc! {"
 5034        «testˇ»
 5035    "});
 5036    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5037    cx.assert_editor_state(indoc! {"
 5038        <«ˇ»>test</«ˇ»>
 5039    "});
 5040
 5041    cx.set_state(indoc! {"
 5042        «test
 5043         testˇ»
 5044    "});
 5045    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5046    cx.assert_editor_state(indoc! {"
 5047        <«ˇ»>test
 5048         test</«ˇ»>
 5049    "});
 5050
 5051    cx.set_state(indoc! {"
 5052        teˇst
 5053    "});
 5054    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5055    cx.assert_editor_state(indoc! {"
 5056        te<«ˇ»></«ˇ»>st
 5057    "});
 5058}
 5059
 5060#[gpui::test]
 5061async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5062    init_test(cx, |_| {});
 5063
 5064    let mut cx = EditorTestContext::new(cx).await;
 5065
 5066    let js_language = Arc::new(Language::new(
 5067        LanguageConfig {
 5068            name: "JavaScript".into(),
 5069            wrap_characters: Some(language::WrapCharactersConfig {
 5070                start_prefix: "<".into(),
 5071                start_suffix: ">".into(),
 5072                end_prefix: "</".into(),
 5073                end_suffix: ">".into(),
 5074            }),
 5075            ..LanguageConfig::default()
 5076        },
 5077        None,
 5078    ));
 5079
 5080    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5081
 5082    cx.set_state(indoc! {"
 5083        «testˇ»
 5084        «testˇ» «testˇ»
 5085        «testˇ»
 5086    "});
 5087    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5088    cx.assert_editor_state(indoc! {"
 5089        <«ˇ»>test</«ˇ»>
 5090        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5091        <«ˇ»>test</«ˇ»>
 5092    "});
 5093
 5094    cx.set_state(indoc! {"
 5095        «test
 5096         testˇ»
 5097        «test
 5098         testˇ»
 5099    "});
 5100    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5101    cx.assert_editor_state(indoc! {"
 5102        <«ˇ»>test
 5103         test</«ˇ»>
 5104        <«ˇ»>test
 5105         test</«ˇ»>
 5106    "});
 5107}
 5108
 5109#[gpui::test]
 5110async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5111    init_test(cx, |_| {});
 5112
 5113    let mut cx = EditorTestContext::new(cx).await;
 5114
 5115    let plaintext_language = Arc::new(Language::new(
 5116        LanguageConfig {
 5117            name: "Plain Text".into(),
 5118            ..LanguageConfig::default()
 5119        },
 5120        None,
 5121    ));
 5122
 5123    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5124
 5125    cx.set_state(indoc! {"
 5126        «testˇ»
 5127    "});
 5128    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5129    cx.assert_editor_state(indoc! {"
 5130      «testˇ»
 5131    "});
 5132}
 5133
 5134#[gpui::test]
 5135async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5136    init_test(cx, |_| {});
 5137
 5138    let mut cx = EditorTestContext::new(cx).await;
 5139
 5140    // Manipulate with multiple selections on a single line
 5141    cx.set_state(indoc! {"
 5142        dd«dd
 5143        cˇ»c«c
 5144        bb
 5145        aaaˇ»aa
 5146    "});
 5147    cx.update_editor(|e, window, cx| {
 5148        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5149    });
 5150    cx.assert_editor_state(indoc! {"
 5151        «aaaaa
 5152        bb
 5153        ccc
 5154        ddddˇ»
 5155    "});
 5156
 5157    // Manipulate with multiple disjoin selections
 5158    cx.set_state(indoc! {"
 5159 5160        4
 5161        3
 5162        2
 5163        1ˇ»
 5164
 5165        dd«dd
 5166        ccc
 5167        bb
 5168        aaaˇ»aa
 5169    "});
 5170    cx.update_editor(|e, window, cx| {
 5171        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5172    });
 5173    cx.assert_editor_state(indoc! {"
 5174        «1
 5175        2
 5176        3
 5177        4
 5178        5ˇ»
 5179
 5180        «aaaaa
 5181        bb
 5182        ccc
 5183        ddddˇ»
 5184    "});
 5185
 5186    // Adding lines on each selection
 5187    cx.set_state(indoc! {"
 5188 5189        1ˇ»
 5190
 5191        bb«bb
 5192        aaaˇ»aa
 5193    "});
 5194    cx.update_editor(|e, window, cx| {
 5195        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5196    });
 5197    cx.assert_editor_state(indoc! {"
 5198        «2
 5199        1
 5200        added lineˇ»
 5201
 5202        «bbbb
 5203        aaaaa
 5204        added lineˇ»
 5205    "});
 5206
 5207    // Removing lines on each selection
 5208    cx.set_state(indoc! {"
 5209 5210        1ˇ»
 5211
 5212        bb«bb
 5213        aaaˇ»aa
 5214    "});
 5215    cx.update_editor(|e, window, cx| {
 5216        e.manipulate_immutable_lines(window, cx, |lines| {
 5217            lines.pop();
 5218        })
 5219    });
 5220    cx.assert_editor_state(indoc! {"
 5221        «2ˇ»
 5222
 5223        «bbbbˇ»
 5224    "});
 5225}
 5226
 5227#[gpui::test]
 5228async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5229    init_test(cx, |settings| {
 5230        settings.defaults.tab_size = NonZeroU32::new(3)
 5231    });
 5232
 5233    let mut cx = EditorTestContext::new(cx).await;
 5234
 5235    // MULTI SELECTION
 5236    // Ln.1 "«" tests empty lines
 5237    // Ln.9 tests just leading whitespace
 5238    cx.set_state(indoc! {"
 5239        «
 5240        abc                 // No indentationˇ»
 5241        «\tabc              // 1 tabˇ»
 5242        \t\tabc «      ˇ»   // 2 tabs
 5243        \t ab«c             // Tab followed by space
 5244         \tabc              // Space followed by tab (3 spaces should be the result)
 5245        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5246           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5247        \t
 5248        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5249    "});
 5250    cx.update_editor(|e, window, cx| {
 5251        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5252    });
 5253    cx.assert_editor_state(
 5254        indoc! {"
 5255            «
 5256            abc                 // No indentation
 5257               abc              // 1 tab
 5258                  abc          // 2 tabs
 5259                abc             // Tab followed by space
 5260               abc              // Space followed by tab (3 spaces should be the result)
 5261                           abc   // Mixed indentation (tab conversion depends on the column)
 5262               abc         // Already space indented
 5263               ·
 5264               abc\tdef          // Only the leading tab is manipulatedˇ»
 5265        "}
 5266        .replace("·", "")
 5267        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5268    );
 5269
 5270    // Test on just a few lines, the others should remain unchanged
 5271    // Only lines (3, 5, 10, 11) should change
 5272    cx.set_state(
 5273        indoc! {"
 5274            ·
 5275            abc                 // No indentation
 5276            \tabcˇ               // 1 tab
 5277            \t\tabc             // 2 tabs
 5278            \t abcˇ              // Tab followed by space
 5279             \tabc              // Space followed by tab (3 spaces should be the result)
 5280            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5281               abc              // Already space indented
 5282            «\t
 5283            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5284        "}
 5285        .replace("·", "")
 5286        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5287    );
 5288    cx.update_editor(|e, window, cx| {
 5289        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5290    });
 5291    cx.assert_editor_state(
 5292        indoc! {"
 5293            ·
 5294            abc                 // No indentation
 5295            «   abc               // 1 tabˇ»
 5296            \t\tabc             // 2 tabs
 5297            «    abc              // Tab followed by spaceˇ»
 5298             \tabc              // Space followed by tab (3 spaces should be the result)
 5299            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5300               abc              // Already space indented
 5301            «   ·
 5302               abc\tdef          // Only the leading tab is manipulatedˇ»
 5303        "}
 5304        .replace("·", "")
 5305        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5306    );
 5307
 5308    // SINGLE SELECTION
 5309    // Ln.1 "«" tests empty lines
 5310    // Ln.9 tests just leading whitespace
 5311    cx.set_state(indoc! {"
 5312        «
 5313        abc                 // No indentation
 5314        \tabc               // 1 tab
 5315        \t\tabc             // 2 tabs
 5316        \t abc              // Tab followed by space
 5317         \tabc              // Space followed by tab (3 spaces should be the result)
 5318        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5319           abc              // Already space indented
 5320        \t
 5321        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5322    "});
 5323    cx.update_editor(|e, window, cx| {
 5324        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5325    });
 5326    cx.assert_editor_state(
 5327        indoc! {"
 5328            «
 5329            abc                 // No indentation
 5330               abc               // 1 tab
 5331                  abc             // 2 tabs
 5332                abc              // Tab followed by space
 5333               abc              // Space followed by tab (3 spaces should be the result)
 5334                           abc   // Mixed indentation (tab conversion depends on the column)
 5335               abc              // Already space indented
 5336               ·
 5337               abc\tdef          // Only the leading tab is manipulatedˇ»
 5338        "}
 5339        .replace("·", "")
 5340        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5341    );
 5342}
 5343
 5344#[gpui::test]
 5345async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5346    init_test(cx, |settings| {
 5347        settings.defaults.tab_size = NonZeroU32::new(3)
 5348    });
 5349
 5350    let mut cx = EditorTestContext::new(cx).await;
 5351
 5352    // MULTI SELECTION
 5353    // Ln.1 "«" tests empty lines
 5354    // Ln.11 tests just leading whitespace
 5355    cx.set_state(indoc! {"
 5356        «
 5357        abˇ»ˇc                 // No indentation
 5358         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5359          abc  «             // 2 spaces (< 3 so dont convert)
 5360           abc              // 3 spaces (convert)
 5361             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5362        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5363        «\t abc              // Tab followed by space
 5364         \tabc              // Space followed by tab (should be consumed due to tab)
 5365        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5366           \tˇ»  «\t
 5367           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5368    "});
 5369    cx.update_editor(|e, window, cx| {
 5370        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5371    });
 5372    cx.assert_editor_state(indoc! {"
 5373        «
 5374        abc                 // No indentation
 5375         abc                // 1 space (< 3 so dont convert)
 5376          abc               // 2 spaces (< 3 so dont convert)
 5377        \tabc              // 3 spaces (convert)
 5378        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5379        \t\t\tabc           // Already tab indented
 5380        \t abc              // Tab followed by space
 5381        \tabc              // Space followed by tab (should be consumed due to tab)
 5382        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5383        \t\t\t
 5384        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5385    "});
 5386
 5387    // Test on just a few lines, the other should remain unchanged
 5388    // Only lines (4, 8, 11, 12) should change
 5389    cx.set_state(
 5390        indoc! {"
 5391            ·
 5392            abc                 // No indentation
 5393             abc                // 1 space (< 3 so dont convert)
 5394              abc               // 2 spaces (< 3 so dont convert)
 5395            «   abc              // 3 spaces (convert)ˇ»
 5396                 abc            // 5 spaces (1 tab + 2 spaces)
 5397            \t\t\tabc           // Already tab indented
 5398            \t abc              // Tab followed by space
 5399             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5400               \t\t  \tabc      // Mixed indentation
 5401            \t \t  \t   \tabc   // Mixed indentation
 5402               \t  \tˇ
 5403            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5404        "}
 5405        .replace("·", "")
 5406        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5407    );
 5408    cx.update_editor(|e, window, cx| {
 5409        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5410    });
 5411    cx.assert_editor_state(
 5412        indoc! {"
 5413            ·
 5414            abc                 // No indentation
 5415             abc                // 1 space (< 3 so dont convert)
 5416              abc               // 2 spaces (< 3 so dont convert)
 5417            «\tabc              // 3 spaces (convert)ˇ»
 5418                 abc            // 5 spaces (1 tab + 2 spaces)
 5419            \t\t\tabc           // Already tab indented
 5420            \t abc              // Tab followed by space
 5421            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5422               \t\t  \tabc      // Mixed indentation
 5423            \t \t  \t   \tabc   // Mixed indentation
 5424            «\t\t\t
 5425            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5426        "}
 5427        .replace("·", "")
 5428        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5429    );
 5430
 5431    // SINGLE SELECTION
 5432    // Ln.1 "«" tests empty lines
 5433    // Ln.11 tests just leading whitespace
 5434    cx.set_state(indoc! {"
 5435        «
 5436        abc                 // No indentation
 5437         abc                // 1 space (< 3 so dont convert)
 5438          abc               // 2 spaces (< 3 so dont convert)
 5439           abc              // 3 spaces (convert)
 5440             abc            // 5 spaces (1 tab + 2 spaces)
 5441        \t\t\tabc           // Already tab indented
 5442        \t abc              // Tab followed by space
 5443         \tabc              // Space followed by tab (should be consumed due to tab)
 5444        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5445           \t  \t
 5446           abc   \t         // Only the leading spaces should be convertedˇ»
 5447    "});
 5448    cx.update_editor(|e, window, cx| {
 5449        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5450    });
 5451    cx.assert_editor_state(indoc! {"
 5452        «
 5453        abc                 // No indentation
 5454         abc                // 1 space (< 3 so dont convert)
 5455          abc               // 2 spaces (< 3 so dont convert)
 5456        \tabc              // 3 spaces (convert)
 5457        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5458        \t\t\tabc           // Already tab indented
 5459        \t abc              // Tab followed by space
 5460        \tabc              // Space followed by tab (should be consumed due to tab)
 5461        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5462        \t\t\t
 5463        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5464    "});
 5465}
 5466
 5467#[gpui::test]
 5468async fn test_toggle_case(cx: &mut TestAppContext) {
 5469    init_test(cx, |_| {});
 5470
 5471    let mut cx = EditorTestContext::new(cx).await;
 5472
 5473    // If all lower case -> upper case
 5474    cx.set_state(indoc! {"
 5475        «hello worldˇ»
 5476    "});
 5477    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5478    cx.assert_editor_state(indoc! {"
 5479        «HELLO WORLDˇ»
 5480    "});
 5481
 5482    // If all upper case -> lower case
 5483    cx.set_state(indoc! {"
 5484        «HELLO WORLDˇ»
 5485    "});
 5486    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5487    cx.assert_editor_state(indoc! {"
 5488        «hello worldˇ»
 5489    "});
 5490
 5491    // If any upper case characters are identified -> lower case
 5492    // This matches JetBrains IDEs
 5493    cx.set_state(indoc! {"
 5494        «hEllo worldˇ»
 5495    "});
 5496    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5497    cx.assert_editor_state(indoc! {"
 5498        «hello worldˇ»
 5499    "});
 5500}
 5501
 5502#[gpui::test]
 5503async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5504    init_test(cx, |_| {});
 5505
 5506    let mut cx = EditorTestContext::new(cx).await;
 5507
 5508    cx.set_state(indoc! {"
 5509        «implement-windows-supportˇ»
 5510    "});
 5511    cx.update_editor(|e, window, cx| {
 5512        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5513    });
 5514    cx.assert_editor_state(indoc! {"
 5515        «Implement windows supportˇ»
 5516    "});
 5517}
 5518
 5519#[gpui::test]
 5520async fn test_manipulate_text(cx: &mut TestAppContext) {
 5521    init_test(cx, |_| {});
 5522
 5523    let mut cx = EditorTestContext::new(cx).await;
 5524
 5525    // Test convert_to_upper_case()
 5526    cx.set_state(indoc! {"
 5527        «hello worldˇ»
 5528    "});
 5529    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5530    cx.assert_editor_state(indoc! {"
 5531        «HELLO WORLDˇ»
 5532    "});
 5533
 5534    // Test convert_to_lower_case()
 5535    cx.set_state(indoc! {"
 5536        «HELLO WORLDˇ»
 5537    "});
 5538    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5539    cx.assert_editor_state(indoc! {"
 5540        «hello worldˇ»
 5541    "});
 5542
 5543    // Test multiple line, single selection case
 5544    cx.set_state(indoc! {"
 5545        «The quick brown
 5546        fox jumps over
 5547        the lazy dogˇ»
 5548    "});
 5549    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5550    cx.assert_editor_state(indoc! {"
 5551        «The Quick Brown
 5552        Fox Jumps Over
 5553        The Lazy Dogˇ»
 5554    "});
 5555
 5556    // Test multiple line, single selection case
 5557    cx.set_state(indoc! {"
 5558        «The quick brown
 5559        fox jumps over
 5560        the lazy dogˇ»
 5561    "});
 5562    cx.update_editor(|e, window, cx| {
 5563        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5564    });
 5565    cx.assert_editor_state(indoc! {"
 5566        «TheQuickBrown
 5567        FoxJumpsOver
 5568        TheLazyDogˇ»
 5569    "});
 5570
 5571    // From here on out, test more complex cases of manipulate_text()
 5572
 5573    // Test no selection case - should affect words cursors are in
 5574    // Cursor at beginning, middle, and end of word
 5575    cx.set_state(indoc! {"
 5576        ˇhello big beauˇtiful worldˇ
 5577    "});
 5578    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5579    cx.assert_editor_state(indoc! {"
 5580        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5581    "});
 5582
 5583    // Test multiple selections on a single line and across multiple lines
 5584    cx.set_state(indoc! {"
 5585        «Theˇ» quick «brown
 5586        foxˇ» jumps «overˇ»
 5587        the «lazyˇ» dog
 5588    "});
 5589    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5590    cx.assert_editor_state(indoc! {"
 5591        «THEˇ» quick «BROWN
 5592        FOXˇ» jumps «OVERˇ»
 5593        the «LAZYˇ» dog
 5594    "});
 5595
 5596    // Test case where text length grows
 5597    cx.set_state(indoc! {"
 5598        «tschüߡ»
 5599    "});
 5600    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5601    cx.assert_editor_state(indoc! {"
 5602        «TSCHÜSSˇ»
 5603    "});
 5604
 5605    // Test to make sure we don't crash when text shrinks
 5606    cx.set_state(indoc! {"
 5607        aaa_bbbˇ
 5608    "});
 5609    cx.update_editor(|e, window, cx| {
 5610        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5611    });
 5612    cx.assert_editor_state(indoc! {"
 5613        «aaaBbbˇ»
 5614    "});
 5615
 5616    // Test to make sure we all aware of the fact that each word can grow and shrink
 5617    // Final selections should be aware of this fact
 5618    cx.set_state(indoc! {"
 5619        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5620    "});
 5621    cx.update_editor(|e, window, cx| {
 5622        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5623    });
 5624    cx.assert_editor_state(indoc! {"
 5625        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5626    "});
 5627
 5628    cx.set_state(indoc! {"
 5629        «hElLo, WoRld!ˇ»
 5630    "});
 5631    cx.update_editor(|e, window, cx| {
 5632        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5633    });
 5634    cx.assert_editor_state(indoc! {"
 5635        «HeLlO, wOrLD!ˇ»
 5636    "});
 5637
 5638    // Test selections with `line_mode() = true`.
 5639    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5640    cx.set_state(indoc! {"
 5641        «The quick brown
 5642        fox jumps over
 5643        tˇ»he lazy dog
 5644    "});
 5645    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5646    cx.assert_editor_state(indoc! {"
 5647        «THE QUICK BROWN
 5648        FOX JUMPS OVER
 5649        THE LAZY DOGˇ»
 5650    "});
 5651}
 5652
 5653#[gpui::test]
 5654fn test_duplicate_line(cx: &mut TestAppContext) {
 5655    init_test(cx, |_| {});
 5656
 5657    let editor = cx.add_window(|window, cx| {
 5658        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5659        build_editor(buffer, window, cx)
 5660    });
 5661    _ = editor.update(cx, |editor, window, cx| {
 5662        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5663            s.select_display_ranges([
 5664                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5665                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5666                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5667                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5668            ])
 5669        });
 5670        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5671        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5672        assert_eq!(
 5673            display_ranges(editor, cx),
 5674            vec![
 5675                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5676                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5677                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5678                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5679            ]
 5680        );
 5681    });
 5682
 5683    let editor = cx.add_window(|window, cx| {
 5684        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5685        build_editor(buffer, window, cx)
 5686    });
 5687    _ = editor.update(cx, |editor, window, cx| {
 5688        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5689            s.select_display_ranges([
 5690                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5691                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5692            ])
 5693        });
 5694        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5695        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5696        assert_eq!(
 5697            display_ranges(editor, cx),
 5698            vec![
 5699                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5700                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5701            ]
 5702        );
 5703    });
 5704
 5705    // With `duplicate_line_up` the selections move to the duplicated lines,
 5706    // which are inserted above the original lines
 5707    let editor = cx.add_window(|window, cx| {
 5708        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5709        build_editor(buffer, window, cx)
 5710    });
 5711    _ = editor.update(cx, |editor, window, cx| {
 5712        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5713            s.select_display_ranges([
 5714                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5715                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5716                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5717                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5718            ])
 5719        });
 5720        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5721        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5722        assert_eq!(
 5723            display_ranges(editor, cx),
 5724            vec![
 5725                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5726                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5727                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5728                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5729            ]
 5730        );
 5731    });
 5732
 5733    let editor = cx.add_window(|window, cx| {
 5734        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5735        build_editor(buffer, window, cx)
 5736    });
 5737    _ = editor.update(cx, |editor, window, cx| {
 5738        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5739            s.select_display_ranges([
 5740                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5741                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5742            ])
 5743        });
 5744        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5745        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5746        assert_eq!(
 5747            display_ranges(editor, cx),
 5748            vec![
 5749                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5750                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5751            ]
 5752        );
 5753    });
 5754
 5755    let editor = cx.add_window(|window, cx| {
 5756        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5757        build_editor(buffer, window, cx)
 5758    });
 5759    _ = editor.update(cx, |editor, window, cx| {
 5760        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5761            s.select_display_ranges([
 5762                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5763                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5764            ])
 5765        });
 5766        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5767        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5768        assert_eq!(
 5769            display_ranges(editor, cx),
 5770            vec![
 5771                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5772                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5773            ]
 5774        );
 5775    });
 5776}
 5777
 5778#[gpui::test]
 5779fn test_move_line_up_down(cx: &mut TestAppContext) {
 5780    init_test(cx, |_| {});
 5781
 5782    let editor = cx.add_window(|window, cx| {
 5783        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5784        build_editor(buffer, window, cx)
 5785    });
 5786    _ = editor.update(cx, |editor, window, cx| {
 5787        editor.fold_creases(
 5788            vec![
 5789                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5790                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5791                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5792            ],
 5793            true,
 5794            window,
 5795            cx,
 5796        );
 5797        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5798            s.select_display_ranges([
 5799                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5800                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5801                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5802                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5803            ])
 5804        });
 5805        assert_eq!(
 5806            editor.display_text(cx),
 5807            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5808        );
 5809
 5810        editor.move_line_up(&MoveLineUp, window, cx);
 5811        assert_eq!(
 5812            editor.display_text(cx),
 5813            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5814        );
 5815        assert_eq!(
 5816            display_ranges(editor, cx),
 5817            vec![
 5818                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5819                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5820                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5821                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5822            ]
 5823        );
 5824    });
 5825
 5826    _ = editor.update(cx, |editor, window, cx| {
 5827        editor.move_line_down(&MoveLineDown, window, cx);
 5828        assert_eq!(
 5829            editor.display_text(cx),
 5830            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5831        );
 5832        assert_eq!(
 5833            display_ranges(editor, cx),
 5834            vec![
 5835                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5836                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5837                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5838                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5839            ]
 5840        );
 5841    });
 5842
 5843    _ = editor.update(cx, |editor, window, cx| {
 5844        editor.move_line_down(&MoveLineDown, window, cx);
 5845        assert_eq!(
 5846            editor.display_text(cx),
 5847            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5848        );
 5849        assert_eq!(
 5850            display_ranges(editor, cx),
 5851            vec![
 5852                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5853                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5854                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5855                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5856            ]
 5857        );
 5858    });
 5859
 5860    _ = editor.update(cx, |editor, window, cx| {
 5861        editor.move_line_up(&MoveLineUp, window, cx);
 5862        assert_eq!(
 5863            editor.display_text(cx),
 5864            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5865        );
 5866        assert_eq!(
 5867            display_ranges(editor, cx),
 5868            vec![
 5869                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5870                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5871                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5872                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5873            ]
 5874        );
 5875    });
 5876}
 5877
 5878#[gpui::test]
 5879fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5880    init_test(cx, |_| {});
 5881    let editor = cx.add_window(|window, cx| {
 5882        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5883        build_editor(buffer, window, cx)
 5884    });
 5885    _ = editor.update(cx, |editor, window, cx| {
 5886        editor.fold_creases(
 5887            vec![Crease::simple(
 5888                Point::new(6, 4)..Point::new(7, 4),
 5889                FoldPlaceholder::test(),
 5890            )],
 5891            true,
 5892            window,
 5893            cx,
 5894        );
 5895        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5896            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5897        });
 5898        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5899        editor.move_line_up(&MoveLineUp, window, cx);
 5900        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5901        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5902    });
 5903}
 5904
 5905#[gpui::test]
 5906fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5907    init_test(cx, |_| {});
 5908
 5909    let editor = cx.add_window(|window, cx| {
 5910        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5911        build_editor(buffer, window, cx)
 5912    });
 5913    _ = editor.update(cx, |editor, window, cx| {
 5914        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5915        editor.insert_blocks(
 5916            [BlockProperties {
 5917                style: BlockStyle::Fixed,
 5918                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5919                height: Some(1),
 5920                render: Arc::new(|_| div().into_any()),
 5921                priority: 0,
 5922            }],
 5923            Some(Autoscroll::fit()),
 5924            cx,
 5925        );
 5926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5927            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5928        });
 5929        editor.move_line_down(&MoveLineDown, window, cx);
 5930    });
 5931}
 5932
 5933#[gpui::test]
 5934async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5935    init_test(cx, |_| {});
 5936
 5937    let mut cx = EditorTestContext::new(cx).await;
 5938    cx.set_state(
 5939        &"
 5940            ˇzero
 5941            one
 5942            two
 5943            three
 5944            four
 5945            five
 5946        "
 5947        .unindent(),
 5948    );
 5949
 5950    // Create a four-line block that replaces three lines of text.
 5951    cx.update_editor(|editor, window, cx| {
 5952        let snapshot = editor.snapshot(window, cx);
 5953        let snapshot = &snapshot.buffer_snapshot();
 5954        let placement = BlockPlacement::Replace(
 5955            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5956        );
 5957        editor.insert_blocks(
 5958            [BlockProperties {
 5959                placement,
 5960                height: Some(4),
 5961                style: BlockStyle::Sticky,
 5962                render: Arc::new(|_| gpui::div().into_any_element()),
 5963                priority: 0,
 5964            }],
 5965            None,
 5966            cx,
 5967        );
 5968    });
 5969
 5970    // Move down so that the cursor touches the block.
 5971    cx.update_editor(|editor, window, cx| {
 5972        editor.move_down(&Default::default(), window, cx);
 5973    });
 5974    cx.assert_editor_state(
 5975        &"
 5976            zero
 5977            «one
 5978            two
 5979            threeˇ»
 5980            four
 5981            five
 5982        "
 5983        .unindent(),
 5984    );
 5985
 5986    // Move down past the block.
 5987    cx.update_editor(|editor, window, cx| {
 5988        editor.move_down(&Default::default(), window, cx);
 5989    });
 5990    cx.assert_editor_state(
 5991        &"
 5992            zero
 5993            one
 5994            two
 5995            three
 5996            ˇfour
 5997            five
 5998        "
 5999        .unindent(),
 6000    );
 6001}
 6002
 6003#[gpui::test]
 6004fn test_transpose(cx: &mut TestAppContext) {
 6005    init_test(cx, |_| {});
 6006
 6007    _ = cx.add_window(|window, cx| {
 6008        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6009        editor.set_style(EditorStyle::default(), window, cx);
 6010        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6011            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6012        });
 6013        editor.transpose(&Default::default(), window, cx);
 6014        assert_eq!(editor.text(cx), "bac");
 6015        assert_eq!(
 6016            editor.selections.ranges(&editor.display_snapshot(cx)),
 6017            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6018        );
 6019
 6020        editor.transpose(&Default::default(), window, cx);
 6021        assert_eq!(editor.text(cx), "bca");
 6022        assert_eq!(
 6023            editor.selections.ranges(&editor.display_snapshot(cx)),
 6024            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6025        );
 6026
 6027        editor.transpose(&Default::default(), window, cx);
 6028        assert_eq!(editor.text(cx), "bac");
 6029        assert_eq!(
 6030            editor.selections.ranges(&editor.display_snapshot(cx)),
 6031            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6032        );
 6033
 6034        editor
 6035    });
 6036
 6037    _ = cx.add_window(|window, cx| {
 6038        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6039        editor.set_style(EditorStyle::default(), window, cx);
 6040        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6041            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6042        });
 6043        editor.transpose(&Default::default(), window, cx);
 6044        assert_eq!(editor.text(cx), "acb\nde");
 6045        assert_eq!(
 6046            editor.selections.ranges(&editor.display_snapshot(cx)),
 6047            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6048        );
 6049
 6050        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6051            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6052        });
 6053        editor.transpose(&Default::default(), window, cx);
 6054        assert_eq!(editor.text(cx), "acbd\ne");
 6055        assert_eq!(
 6056            editor.selections.ranges(&editor.display_snapshot(cx)),
 6057            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6058        );
 6059
 6060        editor.transpose(&Default::default(), window, cx);
 6061        assert_eq!(editor.text(cx), "acbde\n");
 6062        assert_eq!(
 6063            editor.selections.ranges(&editor.display_snapshot(cx)),
 6064            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6065        );
 6066
 6067        editor.transpose(&Default::default(), window, cx);
 6068        assert_eq!(editor.text(cx), "acbd\ne");
 6069        assert_eq!(
 6070            editor.selections.ranges(&editor.display_snapshot(cx)),
 6071            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6072        );
 6073
 6074        editor
 6075    });
 6076
 6077    _ = cx.add_window(|window, cx| {
 6078        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6079        editor.set_style(EditorStyle::default(), window, cx);
 6080        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6081            s.select_ranges([
 6082                MultiBufferOffset(1)..MultiBufferOffset(1),
 6083                MultiBufferOffset(2)..MultiBufferOffset(2),
 6084                MultiBufferOffset(4)..MultiBufferOffset(4),
 6085            ])
 6086        });
 6087        editor.transpose(&Default::default(), window, cx);
 6088        assert_eq!(editor.text(cx), "bacd\ne");
 6089        assert_eq!(
 6090            editor.selections.ranges(&editor.display_snapshot(cx)),
 6091            [
 6092                MultiBufferOffset(2)..MultiBufferOffset(2),
 6093                MultiBufferOffset(3)..MultiBufferOffset(3),
 6094                MultiBufferOffset(5)..MultiBufferOffset(5)
 6095            ]
 6096        );
 6097
 6098        editor.transpose(&Default::default(), window, cx);
 6099        assert_eq!(editor.text(cx), "bcade\n");
 6100        assert_eq!(
 6101            editor.selections.ranges(&editor.display_snapshot(cx)),
 6102            [
 6103                MultiBufferOffset(3)..MultiBufferOffset(3),
 6104                MultiBufferOffset(4)..MultiBufferOffset(4),
 6105                MultiBufferOffset(6)..MultiBufferOffset(6)
 6106            ]
 6107        );
 6108
 6109        editor.transpose(&Default::default(), window, cx);
 6110        assert_eq!(editor.text(cx), "bcda\ne");
 6111        assert_eq!(
 6112            editor.selections.ranges(&editor.display_snapshot(cx)),
 6113            [
 6114                MultiBufferOffset(4)..MultiBufferOffset(4),
 6115                MultiBufferOffset(6)..MultiBufferOffset(6)
 6116            ]
 6117        );
 6118
 6119        editor.transpose(&Default::default(), window, cx);
 6120        assert_eq!(editor.text(cx), "bcade\n");
 6121        assert_eq!(
 6122            editor.selections.ranges(&editor.display_snapshot(cx)),
 6123            [
 6124                MultiBufferOffset(4)..MultiBufferOffset(4),
 6125                MultiBufferOffset(6)..MultiBufferOffset(6)
 6126            ]
 6127        );
 6128
 6129        editor.transpose(&Default::default(), window, cx);
 6130        assert_eq!(editor.text(cx), "bcaed\n");
 6131        assert_eq!(
 6132            editor.selections.ranges(&editor.display_snapshot(cx)),
 6133            [
 6134                MultiBufferOffset(5)..MultiBufferOffset(5),
 6135                MultiBufferOffset(6)..MultiBufferOffset(6)
 6136            ]
 6137        );
 6138
 6139        editor
 6140    });
 6141
 6142    _ = cx.add_window(|window, cx| {
 6143        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6144        editor.set_style(EditorStyle::default(), window, cx);
 6145        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6146            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6147        });
 6148        editor.transpose(&Default::default(), window, cx);
 6149        assert_eq!(editor.text(cx), "🏀🍐✋");
 6150        assert_eq!(
 6151            editor.selections.ranges(&editor.display_snapshot(cx)),
 6152            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6153        );
 6154
 6155        editor.transpose(&Default::default(), window, cx);
 6156        assert_eq!(editor.text(cx), "🏀✋🍐");
 6157        assert_eq!(
 6158            editor.selections.ranges(&editor.display_snapshot(cx)),
 6159            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6160        );
 6161
 6162        editor.transpose(&Default::default(), window, cx);
 6163        assert_eq!(editor.text(cx), "🏀🍐✋");
 6164        assert_eq!(
 6165            editor.selections.ranges(&editor.display_snapshot(cx)),
 6166            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6167        );
 6168
 6169        editor
 6170    });
 6171}
 6172
 6173#[gpui::test]
 6174async fn test_rewrap(cx: &mut TestAppContext) {
 6175    init_test(cx, |settings| {
 6176        settings.languages.0.extend([
 6177            (
 6178                "Markdown".into(),
 6179                LanguageSettingsContent {
 6180                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6181                    preferred_line_length: Some(40),
 6182                    ..Default::default()
 6183                },
 6184            ),
 6185            (
 6186                "Plain Text".into(),
 6187                LanguageSettingsContent {
 6188                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6189                    preferred_line_length: Some(40),
 6190                    ..Default::default()
 6191                },
 6192            ),
 6193            (
 6194                "C++".into(),
 6195                LanguageSettingsContent {
 6196                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6197                    preferred_line_length: Some(40),
 6198                    ..Default::default()
 6199                },
 6200            ),
 6201            (
 6202                "Python".into(),
 6203                LanguageSettingsContent {
 6204                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6205                    preferred_line_length: Some(40),
 6206                    ..Default::default()
 6207                },
 6208            ),
 6209            (
 6210                "Rust".into(),
 6211                LanguageSettingsContent {
 6212                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6213                    preferred_line_length: Some(40),
 6214                    ..Default::default()
 6215                },
 6216            ),
 6217        ])
 6218    });
 6219
 6220    let mut cx = EditorTestContext::new(cx).await;
 6221
 6222    let cpp_language = Arc::new(Language::new(
 6223        LanguageConfig {
 6224            name: "C++".into(),
 6225            line_comments: vec!["// ".into()],
 6226            ..LanguageConfig::default()
 6227        },
 6228        None,
 6229    ));
 6230    let python_language = Arc::new(Language::new(
 6231        LanguageConfig {
 6232            name: "Python".into(),
 6233            line_comments: vec!["# ".into()],
 6234            ..LanguageConfig::default()
 6235        },
 6236        None,
 6237    ));
 6238    let markdown_language = Arc::new(Language::new(
 6239        LanguageConfig {
 6240            name: "Markdown".into(),
 6241            rewrap_prefixes: vec![
 6242                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6243                regex::Regex::new("[-*+]\\s+").unwrap(),
 6244            ],
 6245            ..LanguageConfig::default()
 6246        },
 6247        None,
 6248    ));
 6249    let rust_language = Arc::new(
 6250        Language::new(
 6251            LanguageConfig {
 6252                name: "Rust".into(),
 6253                line_comments: vec!["// ".into(), "/// ".into()],
 6254                ..LanguageConfig::default()
 6255            },
 6256            Some(tree_sitter_rust::LANGUAGE.into()),
 6257        )
 6258        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6259        .unwrap(),
 6260    );
 6261
 6262    let plaintext_language = Arc::new(Language::new(
 6263        LanguageConfig {
 6264            name: "Plain Text".into(),
 6265            ..LanguageConfig::default()
 6266        },
 6267        None,
 6268    ));
 6269
 6270    // Test basic rewrapping of a long line with a cursor
 6271    assert_rewrap(
 6272        indoc! {"
 6273            // ˇThis is a long comment that needs to be wrapped.
 6274        "},
 6275        indoc! {"
 6276            // ˇThis is a long comment that needs to
 6277            // be wrapped.
 6278        "},
 6279        cpp_language.clone(),
 6280        &mut cx,
 6281    );
 6282
 6283    // Test rewrapping a full selection
 6284    assert_rewrap(
 6285        indoc! {"
 6286            «// This selected long comment needs to be wrapped.ˇ»"
 6287        },
 6288        indoc! {"
 6289            «// This selected long comment needs to
 6290            // be wrapped.ˇ»"
 6291        },
 6292        cpp_language.clone(),
 6293        &mut cx,
 6294    );
 6295
 6296    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6297    assert_rewrap(
 6298        indoc! {"
 6299            // ˇThis is the first line.
 6300            // Thisˇ is the second line.
 6301            // This is the thirdˇ line, all part of one paragraph.
 6302         "},
 6303        indoc! {"
 6304            // ˇThis is the first line. Thisˇ is the
 6305            // second line. This is the thirdˇ line,
 6306            // all part of one paragraph.
 6307         "},
 6308        cpp_language.clone(),
 6309        &mut cx,
 6310    );
 6311
 6312    // Test multiple cursors in different paragraphs trigger separate rewraps
 6313    assert_rewrap(
 6314        indoc! {"
 6315            // ˇThis is the first paragraph, first line.
 6316            // ˇThis is the first paragraph, second line.
 6317
 6318            // ˇThis is the second paragraph, first line.
 6319            // ˇThis is the second paragraph, second line.
 6320        "},
 6321        indoc! {"
 6322            // ˇThis is the first paragraph, first
 6323            // line. ˇThis is the first paragraph,
 6324            // second line.
 6325
 6326            // ˇThis is the second paragraph, first
 6327            // line. ˇThis is the second paragraph,
 6328            // second line.
 6329        "},
 6330        cpp_language.clone(),
 6331        &mut cx,
 6332    );
 6333
 6334    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6335    assert_rewrap(
 6336        indoc! {"
 6337            «// A regular long long comment to be wrapped.
 6338            /// A documentation long comment to be wrapped.ˇ»
 6339          "},
 6340        indoc! {"
 6341            «// A regular long long comment to be
 6342            // wrapped.
 6343            /// A documentation long comment to be
 6344            /// wrapped.ˇ»
 6345          "},
 6346        rust_language.clone(),
 6347        &mut cx,
 6348    );
 6349
 6350    // Test that change in indentation level trigger seperate rewraps
 6351    assert_rewrap(
 6352        indoc! {"
 6353            fn foo() {
 6354                «// This is a long comment at the base indent.
 6355                    // This is a long comment at the next indent.ˇ»
 6356            }
 6357        "},
 6358        indoc! {"
 6359            fn foo() {
 6360                «// This is a long comment at the
 6361                // base indent.
 6362                    // This is a long comment at the
 6363                    // next indent.ˇ»
 6364            }
 6365        "},
 6366        rust_language.clone(),
 6367        &mut cx,
 6368    );
 6369
 6370    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6371    assert_rewrap(
 6372        indoc! {"
 6373            # ˇThis is a long comment using a pound sign.
 6374        "},
 6375        indoc! {"
 6376            # ˇThis is a long comment using a pound
 6377            # sign.
 6378        "},
 6379        python_language,
 6380        &mut cx,
 6381    );
 6382
 6383    // Test rewrapping only affects comments, not code even when selected
 6384    assert_rewrap(
 6385        indoc! {"
 6386            «/// This doc comment is long and should be wrapped.
 6387            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6388        "},
 6389        indoc! {"
 6390            «/// This doc comment is long and should
 6391            /// be wrapped.
 6392            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6393        "},
 6394        rust_language.clone(),
 6395        &mut cx,
 6396    );
 6397
 6398    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6399    assert_rewrap(
 6400        indoc! {"
 6401            # Header
 6402
 6403            A long long long line of markdown text to wrap.ˇ
 6404         "},
 6405        indoc! {"
 6406            # Header
 6407
 6408            A long long long line of markdown text
 6409            to wrap.ˇ
 6410         "},
 6411        markdown_language.clone(),
 6412        &mut cx,
 6413    );
 6414
 6415    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6416    assert_rewrap(
 6417        indoc! {"
 6418            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6419            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6420            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6421        "},
 6422        indoc! {"
 6423            «1. This is a numbered list item that is
 6424               very long and needs to be wrapped
 6425               properly.
 6426            2. This is a numbered list item that is
 6427               very long and needs to be wrapped
 6428               properly.
 6429            - This is an unordered list item that is
 6430              also very long and should not merge
 6431              with the numbered item.ˇ»
 6432        "},
 6433        markdown_language.clone(),
 6434        &mut cx,
 6435    );
 6436
 6437    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6438    assert_rewrap(
 6439        indoc! {"
 6440            «1. This is a numbered list item that is
 6441            very long and needs to be wrapped
 6442            properly.
 6443            2. This is a numbered list item that is
 6444            very long and needs to be wrapped
 6445            properly.
 6446            - This is an unordered list item that is
 6447            also very long and should not merge with
 6448            the numbered item.ˇ»
 6449        "},
 6450        indoc! {"
 6451            «1. This is a numbered list item that is
 6452               very long and needs to be wrapped
 6453               properly.
 6454            2. This is a numbered list item that is
 6455               very long and needs to be wrapped
 6456               properly.
 6457            - This is an unordered list item that is
 6458              also very long and should not merge
 6459              with the numbered item.ˇ»
 6460        "},
 6461        markdown_language.clone(),
 6462        &mut cx,
 6463    );
 6464
 6465    // Test that rewrapping maintain indents even when they already exists.
 6466    assert_rewrap(
 6467        indoc! {"
 6468            «1. This is a numbered list
 6469               item that is very long and needs to be wrapped properly.
 6470            2. This is a numbered list
 6471               item that is very long and needs to be wrapped properly.
 6472            - This is an unordered list item that is also very long and
 6473              should not merge with the numbered item.ˇ»
 6474        "},
 6475        indoc! {"
 6476            «1. This is a numbered list item that is
 6477               very long and needs to be wrapped
 6478               properly.
 6479            2. This is a numbered list item that is
 6480               very long and needs to be wrapped
 6481               properly.
 6482            - This is an unordered list item that is
 6483              also very long and should not merge
 6484              with the numbered item.ˇ»
 6485        "},
 6486        markdown_language,
 6487        &mut cx,
 6488    );
 6489
 6490    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6491    assert_rewrap(
 6492        indoc! {"
 6493            ˇThis is a very long line of plain text that will be wrapped.
 6494        "},
 6495        indoc! {"
 6496            ˇThis is a very long line of plain text
 6497            that will be wrapped.
 6498        "},
 6499        plaintext_language.clone(),
 6500        &mut cx,
 6501    );
 6502
 6503    // Test that non-commented code acts as a paragraph boundary within a selection
 6504    assert_rewrap(
 6505        indoc! {"
 6506               «// This is the first long comment block to be wrapped.
 6507               fn my_func(a: u32);
 6508               // This is the second long comment block to be wrapped.ˇ»
 6509           "},
 6510        indoc! {"
 6511               «// This is the first long comment block
 6512               // to be wrapped.
 6513               fn my_func(a: u32);
 6514               // This is the second long comment block
 6515               // to be wrapped.ˇ»
 6516           "},
 6517        rust_language,
 6518        &mut cx,
 6519    );
 6520
 6521    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6522    assert_rewrap(
 6523        indoc! {"
 6524            «ˇThis is a very long line that will be wrapped.
 6525
 6526            This is another paragraph in the same selection.»
 6527
 6528            «\tThis is a very long indented line that will be wrapped.ˇ»
 6529         "},
 6530        indoc! {"
 6531            «ˇThis is a very long line that will be
 6532            wrapped.
 6533
 6534            This is another paragraph in the same
 6535            selection.»
 6536
 6537            «\tThis is a very long indented line
 6538            \tthat will be wrapped.ˇ»
 6539         "},
 6540        plaintext_language,
 6541        &mut cx,
 6542    );
 6543
 6544    // Test that an empty comment line acts as a paragraph boundary
 6545    assert_rewrap(
 6546        indoc! {"
 6547            // ˇThis is a long comment that will be wrapped.
 6548            //
 6549            // And this is another long comment that will also be wrapped.ˇ
 6550         "},
 6551        indoc! {"
 6552            // ˇThis is a long comment that will be
 6553            // wrapped.
 6554            //
 6555            // And this is another long comment that
 6556            // will also be wrapped.ˇ
 6557         "},
 6558        cpp_language,
 6559        &mut cx,
 6560    );
 6561
 6562    #[track_caller]
 6563    fn assert_rewrap(
 6564        unwrapped_text: &str,
 6565        wrapped_text: &str,
 6566        language: Arc<Language>,
 6567        cx: &mut EditorTestContext,
 6568    ) {
 6569        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6570        cx.set_state(unwrapped_text);
 6571        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6572        cx.assert_editor_state(wrapped_text);
 6573    }
 6574}
 6575
 6576#[gpui::test]
 6577async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6578    init_test(cx, |settings| {
 6579        settings.languages.0.extend([(
 6580            "Rust".into(),
 6581            LanguageSettingsContent {
 6582                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6583                preferred_line_length: Some(40),
 6584                ..Default::default()
 6585            },
 6586        )])
 6587    });
 6588
 6589    let mut cx = EditorTestContext::new(cx).await;
 6590
 6591    let rust_lang = Arc::new(
 6592        Language::new(
 6593            LanguageConfig {
 6594                name: "Rust".into(),
 6595                line_comments: vec!["// ".into()],
 6596                block_comment: Some(BlockCommentConfig {
 6597                    start: "/*".into(),
 6598                    end: "*/".into(),
 6599                    prefix: "* ".into(),
 6600                    tab_size: 1,
 6601                }),
 6602                documentation_comment: Some(BlockCommentConfig {
 6603                    start: "/**".into(),
 6604                    end: "*/".into(),
 6605                    prefix: "* ".into(),
 6606                    tab_size: 1,
 6607                }),
 6608
 6609                ..LanguageConfig::default()
 6610            },
 6611            Some(tree_sitter_rust::LANGUAGE.into()),
 6612        )
 6613        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6614        .unwrap(),
 6615    );
 6616
 6617    // regular block comment
 6618    assert_rewrap(
 6619        indoc! {"
 6620            /*
 6621             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6622             */
 6623            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6624        "},
 6625        indoc! {"
 6626            /*
 6627             *ˇ Lorem ipsum dolor sit amet,
 6628             * consectetur adipiscing elit.
 6629             */
 6630            /*
 6631             *ˇ Lorem ipsum dolor sit amet,
 6632             * consectetur adipiscing elit.
 6633             */
 6634        "},
 6635        rust_lang.clone(),
 6636        &mut cx,
 6637    );
 6638
 6639    // indent is respected
 6640    assert_rewrap(
 6641        indoc! {"
 6642            {}
 6643                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6644        "},
 6645        indoc! {"
 6646            {}
 6647                /*
 6648                 *ˇ Lorem ipsum dolor sit amet,
 6649                 * consectetur adipiscing elit.
 6650                 */
 6651        "},
 6652        rust_lang.clone(),
 6653        &mut cx,
 6654    );
 6655
 6656    // short block comments with inline delimiters
 6657    assert_rewrap(
 6658        indoc! {"
 6659            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6660            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6661             */
 6662            /*
 6663             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6664        "},
 6665        indoc! {"
 6666            /*
 6667             *ˇ Lorem ipsum dolor sit amet,
 6668             * consectetur adipiscing elit.
 6669             */
 6670            /*
 6671             *ˇ Lorem ipsum dolor sit amet,
 6672             * consectetur adipiscing elit.
 6673             */
 6674            /*
 6675             *ˇ Lorem ipsum dolor sit amet,
 6676             * consectetur adipiscing elit.
 6677             */
 6678        "},
 6679        rust_lang.clone(),
 6680        &mut cx,
 6681    );
 6682
 6683    // multiline block comment with inline start/end delimiters
 6684    assert_rewrap(
 6685        indoc! {"
 6686            /*ˇ Lorem ipsum dolor sit amet,
 6687             * consectetur adipiscing elit. */
 6688        "},
 6689        indoc! {"
 6690            /*
 6691             *ˇ Lorem ipsum dolor sit amet,
 6692             * consectetur adipiscing elit.
 6693             */
 6694        "},
 6695        rust_lang.clone(),
 6696        &mut cx,
 6697    );
 6698
 6699    // block comment rewrap still respects paragraph bounds
 6700    assert_rewrap(
 6701        indoc! {"
 6702            /*
 6703             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6704             *
 6705             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6706             */
 6707        "},
 6708        indoc! {"
 6709            /*
 6710             *ˇ Lorem ipsum dolor sit amet,
 6711             * consectetur adipiscing elit.
 6712             *
 6713             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6714             */
 6715        "},
 6716        rust_lang.clone(),
 6717        &mut cx,
 6718    );
 6719
 6720    // documentation comments
 6721    assert_rewrap(
 6722        indoc! {"
 6723            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6724            /**
 6725             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6726             */
 6727        "},
 6728        indoc! {"
 6729            /**
 6730             *ˇ Lorem ipsum dolor sit amet,
 6731             * consectetur adipiscing elit.
 6732             */
 6733            /**
 6734             *ˇ Lorem ipsum dolor sit amet,
 6735             * consectetur adipiscing elit.
 6736             */
 6737        "},
 6738        rust_lang.clone(),
 6739        &mut cx,
 6740    );
 6741
 6742    // different, adjacent comments
 6743    assert_rewrap(
 6744        indoc! {"
 6745            /**
 6746             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6747             */
 6748            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6749            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6750        "},
 6751        indoc! {"
 6752            /**
 6753             *ˇ Lorem ipsum dolor sit amet,
 6754             * consectetur adipiscing elit.
 6755             */
 6756            /*
 6757             *ˇ Lorem ipsum dolor sit amet,
 6758             * consectetur adipiscing elit.
 6759             */
 6760            //ˇ Lorem ipsum dolor sit amet,
 6761            // consectetur adipiscing elit.
 6762        "},
 6763        rust_lang.clone(),
 6764        &mut cx,
 6765    );
 6766
 6767    // selection w/ single short block comment
 6768    assert_rewrap(
 6769        indoc! {"
 6770            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6771        "},
 6772        indoc! {"
 6773            «/*
 6774             * Lorem ipsum dolor sit amet,
 6775             * consectetur adipiscing elit.
 6776             */ˇ»
 6777        "},
 6778        rust_lang.clone(),
 6779        &mut cx,
 6780    );
 6781
 6782    // rewrapping a single comment w/ abutting comments
 6783    assert_rewrap(
 6784        indoc! {"
 6785            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6786            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6787        "},
 6788        indoc! {"
 6789            /*
 6790             * ˇLorem ipsum dolor sit amet,
 6791             * consectetur adipiscing elit.
 6792             */
 6793            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6794        "},
 6795        rust_lang.clone(),
 6796        &mut cx,
 6797    );
 6798
 6799    // selection w/ non-abutting short block comments
 6800    assert_rewrap(
 6801        indoc! {"
 6802            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6803
 6804            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6805        "},
 6806        indoc! {"
 6807            «/*
 6808             * Lorem ipsum dolor sit amet,
 6809             * consectetur adipiscing elit.
 6810             */
 6811
 6812            /*
 6813             * Lorem ipsum dolor sit amet,
 6814             * consectetur adipiscing elit.
 6815             */ˇ»
 6816        "},
 6817        rust_lang.clone(),
 6818        &mut cx,
 6819    );
 6820
 6821    // selection of multiline block comments
 6822    assert_rewrap(
 6823        indoc! {"
 6824            «/* Lorem ipsum dolor sit amet,
 6825             * consectetur adipiscing elit. */ˇ»
 6826        "},
 6827        indoc! {"
 6828            «/*
 6829             * Lorem ipsum dolor sit amet,
 6830             * consectetur adipiscing elit.
 6831             */ˇ»
 6832        "},
 6833        rust_lang.clone(),
 6834        &mut cx,
 6835    );
 6836
 6837    // partial selection of multiline block comments
 6838    assert_rewrap(
 6839        indoc! {"
 6840            «/* Lorem ipsum dolor sit amet,ˇ»
 6841             * consectetur adipiscing elit. */
 6842            /* Lorem ipsum dolor sit amet,
 6843             «* consectetur adipiscing elit. */ˇ»
 6844        "},
 6845        indoc! {"
 6846            «/*
 6847             * Lorem ipsum dolor sit amet,ˇ»
 6848             * consectetur adipiscing elit. */
 6849            /* Lorem ipsum dolor sit amet,
 6850             «* consectetur adipiscing elit.
 6851             */ˇ»
 6852        "},
 6853        rust_lang.clone(),
 6854        &mut cx,
 6855    );
 6856
 6857    // selection w/ abutting short block comments
 6858    // TODO: should not be combined; should rewrap as 2 comments
 6859    assert_rewrap(
 6860        indoc! {"
 6861            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6862            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6863        "},
 6864        // desired behavior:
 6865        // indoc! {"
 6866        //     «/*
 6867        //      * Lorem ipsum dolor sit amet,
 6868        //      * consectetur adipiscing elit.
 6869        //      */
 6870        //     /*
 6871        //      * Lorem ipsum dolor sit amet,
 6872        //      * consectetur adipiscing elit.
 6873        //      */ˇ»
 6874        // "},
 6875        // actual behaviour:
 6876        indoc! {"
 6877            «/*
 6878             * Lorem ipsum dolor sit amet,
 6879             * consectetur adipiscing elit. Lorem
 6880             * ipsum dolor sit amet, consectetur
 6881             * adipiscing elit.
 6882             */ˇ»
 6883        "},
 6884        rust_lang.clone(),
 6885        &mut cx,
 6886    );
 6887
 6888    // TODO: same as above, but with delimiters on separate line
 6889    // assert_rewrap(
 6890    //     indoc! {"
 6891    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6892    //          */
 6893    //         /*
 6894    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6895    //     "},
 6896    //     // desired:
 6897    //     // indoc! {"
 6898    //     //     «/*
 6899    //     //      * Lorem ipsum dolor sit amet,
 6900    //     //      * consectetur adipiscing elit.
 6901    //     //      */
 6902    //     //     /*
 6903    //     //      * Lorem ipsum dolor sit amet,
 6904    //     //      * consectetur adipiscing elit.
 6905    //     //      */ˇ»
 6906    //     // "},
 6907    //     // actual: (but with trailing w/s on the empty lines)
 6908    //     indoc! {"
 6909    //         «/*
 6910    //          * Lorem ipsum dolor sit amet,
 6911    //          * consectetur adipiscing elit.
 6912    //          *
 6913    //          */
 6914    //         /*
 6915    //          *
 6916    //          * Lorem ipsum dolor sit amet,
 6917    //          * consectetur adipiscing elit.
 6918    //          */ˇ»
 6919    //     "},
 6920    //     rust_lang.clone(),
 6921    //     &mut cx,
 6922    // );
 6923
 6924    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6925    assert_rewrap(
 6926        indoc! {"
 6927            /*
 6928             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6929             */
 6930            /*
 6931             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6932            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6933        "},
 6934        // desired:
 6935        // indoc! {"
 6936        //     /*
 6937        //      *ˇ Lorem ipsum dolor sit amet,
 6938        //      * consectetur adipiscing elit.
 6939        //      */
 6940        //     /*
 6941        //      *ˇ Lorem ipsum dolor sit amet,
 6942        //      * consectetur adipiscing elit.
 6943        //      */
 6944        //     /*
 6945        //      *ˇ Lorem ipsum dolor sit amet
 6946        //      */ /* consectetur adipiscing elit. */
 6947        // "},
 6948        // actual:
 6949        indoc! {"
 6950            /*
 6951             //ˇ Lorem ipsum dolor sit amet,
 6952             // consectetur adipiscing elit.
 6953             */
 6954            /*
 6955             * //ˇ Lorem ipsum dolor sit amet,
 6956             * consectetur adipiscing elit.
 6957             */
 6958            /*
 6959             *ˇ Lorem ipsum dolor sit amet */ /*
 6960             * consectetur adipiscing elit.
 6961             */
 6962        "},
 6963        rust_lang,
 6964        &mut cx,
 6965    );
 6966
 6967    #[track_caller]
 6968    fn assert_rewrap(
 6969        unwrapped_text: &str,
 6970        wrapped_text: &str,
 6971        language: Arc<Language>,
 6972        cx: &mut EditorTestContext,
 6973    ) {
 6974        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6975        cx.set_state(unwrapped_text);
 6976        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6977        cx.assert_editor_state(wrapped_text);
 6978    }
 6979}
 6980
 6981#[gpui::test]
 6982async fn test_hard_wrap(cx: &mut TestAppContext) {
 6983    init_test(cx, |_| {});
 6984    let mut cx = EditorTestContext::new(cx).await;
 6985
 6986    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6987    cx.update_editor(|editor, _, cx| {
 6988        editor.set_hard_wrap(Some(14), cx);
 6989    });
 6990
 6991    cx.set_state(indoc!(
 6992        "
 6993        one two three ˇ
 6994        "
 6995    ));
 6996    cx.simulate_input("four");
 6997    cx.run_until_parked();
 6998
 6999    cx.assert_editor_state(indoc!(
 7000        "
 7001        one two three
 7002        fourˇ
 7003        "
 7004    ));
 7005
 7006    cx.update_editor(|editor, window, cx| {
 7007        editor.newline(&Default::default(), window, cx);
 7008    });
 7009    cx.run_until_parked();
 7010    cx.assert_editor_state(indoc!(
 7011        "
 7012        one two three
 7013        four
 7014        ˇ
 7015        "
 7016    ));
 7017
 7018    cx.simulate_input("five");
 7019    cx.run_until_parked();
 7020    cx.assert_editor_state(indoc!(
 7021        "
 7022        one two three
 7023        four
 7024        fiveˇ
 7025        "
 7026    ));
 7027
 7028    cx.update_editor(|editor, window, cx| {
 7029        editor.newline(&Default::default(), window, cx);
 7030    });
 7031    cx.run_until_parked();
 7032    cx.simulate_input("# ");
 7033    cx.run_until_parked();
 7034    cx.assert_editor_state(indoc!(
 7035        "
 7036        one two three
 7037        four
 7038        five
 7039        # ˇ
 7040        "
 7041    ));
 7042
 7043    cx.update_editor(|editor, window, cx| {
 7044        editor.newline(&Default::default(), window, cx);
 7045    });
 7046    cx.run_until_parked();
 7047    cx.assert_editor_state(indoc!(
 7048        "
 7049        one two three
 7050        four
 7051        five
 7052        #\x20
 7053 7054        "
 7055    ));
 7056
 7057    cx.simulate_input(" 6");
 7058    cx.run_until_parked();
 7059    cx.assert_editor_state(indoc!(
 7060        "
 7061        one two three
 7062        four
 7063        five
 7064        #
 7065        # 6ˇ
 7066        "
 7067    ));
 7068}
 7069
 7070#[gpui::test]
 7071async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7072    init_test(cx, |_| {});
 7073
 7074    let mut cx = EditorTestContext::new(cx).await;
 7075
 7076    cx.set_state(indoc! {"The quick brownˇ"});
 7077    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7078    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7079
 7080    cx.set_state(indoc! {"The emacs foxˇ"});
 7081    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7082    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7083
 7084    cx.set_state(indoc! {"
 7085        The quick« brownˇ»
 7086        fox jumps overˇ
 7087        the lazy dog"});
 7088    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7089    cx.assert_editor_state(indoc! {"
 7090        The quickˇ
 7091        ˇthe lazy dog"});
 7092
 7093    cx.set_state(indoc! {"
 7094        The quick« brownˇ»
 7095        fox jumps overˇ
 7096        the lazy dog"});
 7097    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7098    cx.assert_editor_state(indoc! {"
 7099        The quickˇ
 7100        fox jumps overˇthe lazy dog"});
 7101
 7102    cx.set_state(indoc! {"
 7103        The quick« brownˇ»
 7104        fox jumps overˇ
 7105        the lazy dog"});
 7106    cx.update_editor(|e, window, cx| {
 7107        e.cut_to_end_of_line(
 7108            &CutToEndOfLine {
 7109                stop_at_newlines: true,
 7110            },
 7111            window,
 7112            cx,
 7113        )
 7114    });
 7115    cx.assert_editor_state(indoc! {"
 7116        The quickˇ
 7117        fox jumps overˇ
 7118        the lazy dog"});
 7119
 7120    cx.set_state(indoc! {"
 7121        The quick« brownˇ»
 7122        fox jumps overˇ
 7123        the lazy dog"});
 7124    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7125    cx.assert_editor_state(indoc! {"
 7126        The quickˇ
 7127        fox jumps overˇthe lazy dog"});
 7128}
 7129
 7130#[gpui::test]
 7131async fn test_clipboard(cx: &mut TestAppContext) {
 7132    init_test(cx, |_| {});
 7133
 7134    let mut cx = EditorTestContext::new(cx).await;
 7135
 7136    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7137    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7138    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7139
 7140    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7141    cx.set_state("two ˇfour ˇsix ˇ");
 7142    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7143    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7144
 7145    // Paste again but with only two cursors. Since the number of cursors doesn't
 7146    // match the number of slices in the clipboard, the entire clipboard text
 7147    // is pasted at each cursor.
 7148    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7149    cx.update_editor(|e, window, cx| {
 7150        e.handle_input("( ", window, cx);
 7151        e.paste(&Paste, window, cx);
 7152        e.handle_input(") ", window, cx);
 7153    });
 7154    cx.assert_editor_state(
 7155        &([
 7156            "( one✅ ",
 7157            "three ",
 7158            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7159            "three ",
 7160            "five ) ˇ",
 7161        ]
 7162        .join("\n")),
 7163    );
 7164
 7165    // Cut with three selections, one of which is full-line.
 7166    cx.set_state(indoc! {"
 7167        1«2ˇ»3
 7168        4ˇ567
 7169        «8ˇ»9"});
 7170    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7171    cx.assert_editor_state(indoc! {"
 7172        1ˇ3
 7173        ˇ9"});
 7174
 7175    // Paste with three selections, noticing how the copied selection that was full-line
 7176    // gets inserted before the second cursor.
 7177    cx.set_state(indoc! {"
 7178        1ˇ3
 7179 7180        «oˇ»ne"});
 7181    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7182    cx.assert_editor_state(indoc! {"
 7183        12ˇ3
 7184        4567
 7185 7186        8ˇne"});
 7187
 7188    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7189    cx.set_state(indoc! {"
 7190        The quick brown
 7191        fox juˇmps over
 7192        the lazy dog"});
 7193    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7194    assert_eq!(
 7195        cx.read_from_clipboard()
 7196            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7197        Some("fox jumps over\n".to_string())
 7198    );
 7199
 7200    // Paste with three selections, noticing how the copied full-line selection is inserted
 7201    // before the empty selections but replaces the selection that is non-empty.
 7202    cx.set_state(indoc! {"
 7203        Tˇhe quick brown
 7204        «foˇ»x jumps over
 7205        tˇhe lazy dog"});
 7206    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7207    cx.assert_editor_state(indoc! {"
 7208        fox jumps over
 7209        Tˇhe quick brown
 7210        fox jumps over
 7211        ˇx jumps over
 7212        fox jumps over
 7213        tˇhe lazy dog"});
 7214}
 7215
 7216#[gpui::test]
 7217async fn test_copy_trim(cx: &mut TestAppContext) {
 7218    init_test(cx, |_| {});
 7219
 7220    let mut cx = EditorTestContext::new(cx).await;
 7221    cx.set_state(
 7222        r#"            «for selection in selections.iter() {
 7223            let mut start = selection.start;
 7224            let mut end = selection.end;
 7225            let is_entire_line = selection.is_empty();
 7226            if is_entire_line {
 7227                start = Point::new(start.row, 0);ˇ»
 7228                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7229            }
 7230        "#,
 7231    );
 7232    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7233    assert_eq!(
 7234        cx.read_from_clipboard()
 7235            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7236        Some(
 7237            "for selection in selections.iter() {
 7238            let mut start = selection.start;
 7239            let mut end = selection.end;
 7240            let is_entire_line = selection.is_empty();
 7241            if is_entire_line {
 7242                start = Point::new(start.row, 0);"
 7243                .to_string()
 7244        ),
 7245        "Regular copying preserves all indentation selected",
 7246    );
 7247    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7248    assert_eq!(
 7249        cx.read_from_clipboard()
 7250            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7251        Some(
 7252            "for selection in selections.iter() {
 7253let mut start = selection.start;
 7254let mut end = selection.end;
 7255let is_entire_line = selection.is_empty();
 7256if is_entire_line {
 7257    start = Point::new(start.row, 0);"
 7258                .to_string()
 7259        ),
 7260        "Copying with stripping should strip all leading whitespaces"
 7261    );
 7262
 7263    cx.set_state(
 7264        r#"       «     for selection in selections.iter() {
 7265            let mut start = selection.start;
 7266            let mut end = selection.end;
 7267            let is_entire_line = selection.is_empty();
 7268            if is_entire_line {
 7269                start = Point::new(start.row, 0);ˇ»
 7270                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7271            }
 7272        "#,
 7273    );
 7274    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7275    assert_eq!(
 7276        cx.read_from_clipboard()
 7277            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7278        Some(
 7279            "     for selection in selections.iter() {
 7280            let mut start = selection.start;
 7281            let mut end = selection.end;
 7282            let is_entire_line = selection.is_empty();
 7283            if is_entire_line {
 7284                start = Point::new(start.row, 0);"
 7285                .to_string()
 7286        ),
 7287        "Regular copying preserves all indentation selected",
 7288    );
 7289    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7290    assert_eq!(
 7291        cx.read_from_clipboard()
 7292            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7293        Some(
 7294            "for selection in selections.iter() {
 7295let mut start = selection.start;
 7296let mut end = selection.end;
 7297let is_entire_line = selection.is_empty();
 7298if is_entire_line {
 7299    start = Point::new(start.row, 0);"
 7300                .to_string()
 7301        ),
 7302        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7303    );
 7304
 7305    cx.set_state(
 7306        r#"       «ˇ     for selection in selections.iter() {
 7307            let mut start = selection.start;
 7308            let mut end = selection.end;
 7309            let is_entire_line = selection.is_empty();
 7310            if is_entire_line {
 7311                start = Point::new(start.row, 0);»
 7312                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7313            }
 7314        "#,
 7315    );
 7316    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7317    assert_eq!(
 7318        cx.read_from_clipboard()
 7319            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7320        Some(
 7321            "     for selection in selections.iter() {
 7322            let mut start = selection.start;
 7323            let mut end = selection.end;
 7324            let is_entire_line = selection.is_empty();
 7325            if is_entire_line {
 7326                start = Point::new(start.row, 0);"
 7327                .to_string()
 7328        ),
 7329        "Regular copying for reverse selection works the same",
 7330    );
 7331    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7332    assert_eq!(
 7333        cx.read_from_clipboard()
 7334            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7335        Some(
 7336            "for selection in selections.iter() {
 7337let mut start = selection.start;
 7338let mut end = selection.end;
 7339let is_entire_line = selection.is_empty();
 7340if is_entire_line {
 7341    start = Point::new(start.row, 0);"
 7342                .to_string()
 7343        ),
 7344        "Copying with stripping for reverse selection works the same"
 7345    );
 7346
 7347    cx.set_state(
 7348        r#"            for selection «in selections.iter() {
 7349            let mut start = selection.start;
 7350            let mut end = selection.end;
 7351            let is_entire_line = selection.is_empty();
 7352            if is_entire_line {
 7353                start = Point::new(start.row, 0);ˇ»
 7354                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7355            }
 7356        "#,
 7357    );
 7358    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7359    assert_eq!(
 7360        cx.read_from_clipboard()
 7361            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7362        Some(
 7363            "in selections.iter() {
 7364            let mut start = selection.start;
 7365            let mut end = selection.end;
 7366            let is_entire_line = selection.is_empty();
 7367            if is_entire_line {
 7368                start = Point::new(start.row, 0);"
 7369                .to_string()
 7370        ),
 7371        "When selecting past the indent, the copying works as usual",
 7372    );
 7373    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7374    assert_eq!(
 7375        cx.read_from_clipboard()
 7376            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7377        Some(
 7378            "in selections.iter() {
 7379            let mut start = selection.start;
 7380            let mut end = selection.end;
 7381            let is_entire_line = selection.is_empty();
 7382            if is_entire_line {
 7383                start = Point::new(start.row, 0);"
 7384                .to_string()
 7385        ),
 7386        "When selecting past the indent, nothing is trimmed"
 7387    );
 7388
 7389    cx.set_state(
 7390        r#"            «for selection in selections.iter() {
 7391            let mut start = selection.start;
 7392
 7393            let mut end = selection.end;
 7394            let is_entire_line = selection.is_empty();
 7395            if is_entire_line {
 7396                start = Point::new(start.row, 0);
 7397ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7398            }
 7399        "#,
 7400    );
 7401    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7402    assert_eq!(
 7403        cx.read_from_clipboard()
 7404            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7405        Some(
 7406            "for selection in selections.iter() {
 7407let mut start = selection.start;
 7408
 7409let mut end = selection.end;
 7410let is_entire_line = selection.is_empty();
 7411if is_entire_line {
 7412    start = Point::new(start.row, 0);
 7413"
 7414            .to_string()
 7415        ),
 7416        "Copying with stripping should ignore empty lines"
 7417    );
 7418}
 7419
 7420#[gpui::test]
 7421async fn test_paste_multiline(cx: &mut TestAppContext) {
 7422    init_test(cx, |_| {});
 7423
 7424    let mut cx = EditorTestContext::new(cx).await;
 7425    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7426
 7427    // Cut an indented block, without the leading whitespace.
 7428    cx.set_state(indoc! {"
 7429        const a: B = (
 7430            c(),
 7431            «d(
 7432                e,
 7433                f
 7434            )ˇ»
 7435        );
 7436    "});
 7437    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7438    cx.assert_editor_state(indoc! {"
 7439        const a: B = (
 7440            c(),
 7441            ˇ
 7442        );
 7443    "});
 7444
 7445    // Paste it at the same position.
 7446    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7447    cx.assert_editor_state(indoc! {"
 7448        const a: B = (
 7449            c(),
 7450            d(
 7451                e,
 7452                f
 7453 7454        );
 7455    "});
 7456
 7457    // Paste it at a line with a lower indent level.
 7458    cx.set_state(indoc! {"
 7459        ˇ
 7460        const a: B = (
 7461            c(),
 7462        );
 7463    "});
 7464    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7465    cx.assert_editor_state(indoc! {"
 7466        d(
 7467            e,
 7468            f
 7469 7470        const a: B = (
 7471            c(),
 7472        );
 7473    "});
 7474
 7475    // Cut an indented block, with the leading whitespace.
 7476    cx.set_state(indoc! {"
 7477        const a: B = (
 7478            c(),
 7479        «    d(
 7480                e,
 7481                f
 7482            )
 7483        ˇ»);
 7484    "});
 7485    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7486    cx.assert_editor_state(indoc! {"
 7487        const a: B = (
 7488            c(),
 7489        ˇ);
 7490    "});
 7491
 7492    // Paste it at the same position.
 7493    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7494    cx.assert_editor_state(indoc! {"
 7495        const a: B = (
 7496            c(),
 7497            d(
 7498                e,
 7499                f
 7500            )
 7501        ˇ);
 7502    "});
 7503
 7504    // Paste it at a line with a higher indent level.
 7505    cx.set_state(indoc! {"
 7506        const a: B = (
 7507            c(),
 7508            d(
 7509                e,
 7510 7511            )
 7512        );
 7513    "});
 7514    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7515    cx.assert_editor_state(indoc! {"
 7516        const a: B = (
 7517            c(),
 7518            d(
 7519                e,
 7520                f    d(
 7521                    e,
 7522                    f
 7523                )
 7524        ˇ
 7525            )
 7526        );
 7527    "});
 7528
 7529    // Copy an indented block, starting mid-line
 7530    cx.set_state(indoc! {"
 7531        const a: B = (
 7532            c(),
 7533            somethin«g(
 7534                e,
 7535                f
 7536            )ˇ»
 7537        );
 7538    "});
 7539    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7540
 7541    // Paste it on a line with a lower indent level
 7542    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7543    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7544    cx.assert_editor_state(indoc! {"
 7545        const a: B = (
 7546            c(),
 7547            something(
 7548                e,
 7549                f
 7550            )
 7551        );
 7552        g(
 7553            e,
 7554            f
 7555"});
 7556}
 7557
 7558#[gpui::test]
 7559async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7560    init_test(cx, |_| {});
 7561
 7562    cx.write_to_clipboard(ClipboardItem::new_string(
 7563        "    d(\n        e\n    );\n".into(),
 7564    ));
 7565
 7566    let mut cx = EditorTestContext::new(cx).await;
 7567    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7568
 7569    cx.set_state(indoc! {"
 7570        fn a() {
 7571            b();
 7572            if c() {
 7573                ˇ
 7574            }
 7575        }
 7576    "});
 7577
 7578    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7579    cx.assert_editor_state(indoc! {"
 7580        fn a() {
 7581            b();
 7582            if c() {
 7583                d(
 7584                    e
 7585                );
 7586        ˇ
 7587            }
 7588        }
 7589    "});
 7590
 7591    cx.set_state(indoc! {"
 7592        fn a() {
 7593            b();
 7594            ˇ
 7595        }
 7596    "});
 7597
 7598    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7599    cx.assert_editor_state(indoc! {"
 7600        fn a() {
 7601            b();
 7602            d(
 7603                e
 7604            );
 7605        ˇ
 7606        }
 7607    "});
 7608}
 7609
 7610#[gpui::test]
 7611fn test_select_all(cx: &mut TestAppContext) {
 7612    init_test(cx, |_| {});
 7613
 7614    let editor = cx.add_window(|window, cx| {
 7615        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7616        build_editor(buffer, window, cx)
 7617    });
 7618    _ = editor.update(cx, |editor, window, cx| {
 7619        editor.select_all(&SelectAll, window, cx);
 7620        assert_eq!(
 7621            display_ranges(editor, cx),
 7622            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7623        );
 7624    });
 7625}
 7626
 7627#[gpui::test]
 7628fn test_select_line(cx: &mut TestAppContext) {
 7629    init_test(cx, |_| {});
 7630
 7631    let editor = cx.add_window(|window, cx| {
 7632        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7633        build_editor(buffer, window, cx)
 7634    });
 7635    _ = editor.update(cx, |editor, window, cx| {
 7636        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7637            s.select_display_ranges([
 7638                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7639                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7640                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7641                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7642            ])
 7643        });
 7644        editor.select_line(&SelectLine, window, cx);
 7645        assert_eq!(
 7646            display_ranges(editor, cx),
 7647            vec![
 7648                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7649                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7650            ]
 7651        );
 7652    });
 7653
 7654    _ = editor.update(cx, |editor, window, cx| {
 7655        editor.select_line(&SelectLine, window, cx);
 7656        assert_eq!(
 7657            display_ranges(editor, cx),
 7658            vec![
 7659                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7660                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7661            ]
 7662        );
 7663    });
 7664
 7665    _ = editor.update(cx, |editor, window, cx| {
 7666        editor.select_line(&SelectLine, window, cx);
 7667        assert_eq!(
 7668            display_ranges(editor, cx),
 7669            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7670        );
 7671    });
 7672}
 7673
 7674#[gpui::test]
 7675async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7676    init_test(cx, |_| {});
 7677    let mut cx = EditorTestContext::new(cx).await;
 7678
 7679    #[track_caller]
 7680    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7681        cx.set_state(initial_state);
 7682        cx.update_editor(|e, window, cx| {
 7683            e.split_selection_into_lines(&Default::default(), window, cx)
 7684        });
 7685        cx.assert_editor_state(expected_state);
 7686    }
 7687
 7688    // Selection starts and ends at the middle of lines, left-to-right
 7689    test(
 7690        &mut cx,
 7691        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7692        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7693    );
 7694    // Same thing, right-to-left
 7695    test(
 7696        &mut cx,
 7697        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7698        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7699    );
 7700
 7701    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7702    test(
 7703        &mut cx,
 7704        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7705        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7706    );
 7707    // Same thing, right-to-left
 7708    test(
 7709        &mut cx,
 7710        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7711        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7712    );
 7713
 7714    // Whole buffer, left-to-right, last line ends with newline
 7715    test(
 7716        &mut cx,
 7717        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7718        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7719    );
 7720    // Same thing, right-to-left
 7721    test(
 7722        &mut cx,
 7723        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7724        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7725    );
 7726
 7727    // Starts at the end of a line, ends at the start of another
 7728    test(
 7729        &mut cx,
 7730        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7731        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7732    );
 7733}
 7734
 7735#[gpui::test]
 7736async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7737    init_test(cx, |_| {});
 7738
 7739    let editor = cx.add_window(|window, cx| {
 7740        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7741        build_editor(buffer, window, cx)
 7742    });
 7743
 7744    // setup
 7745    _ = editor.update(cx, |editor, window, cx| {
 7746        editor.fold_creases(
 7747            vec![
 7748                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7749                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7750                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7751            ],
 7752            true,
 7753            window,
 7754            cx,
 7755        );
 7756        assert_eq!(
 7757            editor.display_text(cx),
 7758            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7759        );
 7760    });
 7761
 7762    _ = editor.update(cx, |editor, window, cx| {
 7763        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7764            s.select_display_ranges([
 7765                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7766                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7767                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7768                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7769            ])
 7770        });
 7771        editor.split_selection_into_lines(&Default::default(), window, cx);
 7772        assert_eq!(
 7773            editor.display_text(cx),
 7774            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7775        );
 7776    });
 7777    EditorTestContext::for_editor(editor, cx)
 7778        .await
 7779        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7780
 7781    _ = editor.update(cx, |editor, window, cx| {
 7782        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7783            s.select_display_ranges([
 7784                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7785            ])
 7786        });
 7787        editor.split_selection_into_lines(&Default::default(), window, cx);
 7788        assert_eq!(
 7789            editor.display_text(cx),
 7790            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7791        );
 7792        assert_eq!(
 7793            display_ranges(editor, cx),
 7794            [
 7795                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7796                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7797                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7798                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7799                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7800                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7801                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7802            ]
 7803        );
 7804    });
 7805    EditorTestContext::for_editor(editor, cx)
 7806        .await
 7807        .assert_editor_state(
 7808            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7809        );
 7810}
 7811
 7812#[gpui::test]
 7813async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7814    init_test(cx, |_| {});
 7815
 7816    let mut cx = EditorTestContext::new(cx).await;
 7817
 7818    cx.set_state(indoc!(
 7819        r#"abc
 7820           defˇghi
 7821
 7822           jk
 7823           nlmo
 7824           "#
 7825    ));
 7826
 7827    cx.update_editor(|editor, window, cx| {
 7828        editor.add_selection_above(&Default::default(), window, cx);
 7829    });
 7830
 7831    cx.assert_editor_state(indoc!(
 7832        r#"abcˇ
 7833           defˇghi
 7834
 7835           jk
 7836           nlmo
 7837           "#
 7838    ));
 7839
 7840    cx.update_editor(|editor, window, cx| {
 7841        editor.add_selection_above(&Default::default(), window, cx);
 7842    });
 7843
 7844    cx.assert_editor_state(indoc!(
 7845        r#"abcˇ
 7846            defˇghi
 7847
 7848            jk
 7849            nlmo
 7850            "#
 7851    ));
 7852
 7853    cx.update_editor(|editor, window, cx| {
 7854        editor.add_selection_below(&Default::default(), window, cx);
 7855    });
 7856
 7857    cx.assert_editor_state(indoc!(
 7858        r#"abc
 7859           defˇghi
 7860
 7861           jk
 7862           nlmo
 7863           "#
 7864    ));
 7865
 7866    cx.update_editor(|editor, window, cx| {
 7867        editor.undo_selection(&Default::default(), window, cx);
 7868    });
 7869
 7870    cx.assert_editor_state(indoc!(
 7871        r#"abcˇ
 7872           defˇghi
 7873
 7874           jk
 7875           nlmo
 7876           "#
 7877    ));
 7878
 7879    cx.update_editor(|editor, window, cx| {
 7880        editor.redo_selection(&Default::default(), window, cx);
 7881    });
 7882
 7883    cx.assert_editor_state(indoc!(
 7884        r#"abc
 7885           defˇghi
 7886
 7887           jk
 7888           nlmo
 7889           "#
 7890    ));
 7891
 7892    cx.update_editor(|editor, window, cx| {
 7893        editor.add_selection_below(&Default::default(), window, cx);
 7894    });
 7895
 7896    cx.assert_editor_state(indoc!(
 7897        r#"abc
 7898           defˇghi
 7899           ˇ
 7900           jk
 7901           nlmo
 7902           "#
 7903    ));
 7904
 7905    cx.update_editor(|editor, window, cx| {
 7906        editor.add_selection_below(&Default::default(), window, cx);
 7907    });
 7908
 7909    cx.assert_editor_state(indoc!(
 7910        r#"abc
 7911           defˇghi
 7912           ˇ
 7913           jkˇ
 7914           nlmo
 7915           "#
 7916    ));
 7917
 7918    cx.update_editor(|editor, window, cx| {
 7919        editor.add_selection_below(&Default::default(), window, cx);
 7920    });
 7921
 7922    cx.assert_editor_state(indoc!(
 7923        r#"abc
 7924           defˇghi
 7925           ˇ
 7926           jkˇ
 7927           nlmˇo
 7928           "#
 7929    ));
 7930
 7931    cx.update_editor(|editor, window, cx| {
 7932        editor.add_selection_below(&Default::default(), window, cx);
 7933    });
 7934
 7935    cx.assert_editor_state(indoc!(
 7936        r#"abc
 7937           defˇghi
 7938           ˇ
 7939           jkˇ
 7940           nlmˇo
 7941           ˇ"#
 7942    ));
 7943
 7944    // change selections
 7945    cx.set_state(indoc!(
 7946        r#"abc
 7947           def«ˇg»hi
 7948
 7949           jk
 7950           nlmo
 7951           "#
 7952    ));
 7953
 7954    cx.update_editor(|editor, window, cx| {
 7955        editor.add_selection_below(&Default::default(), window, cx);
 7956    });
 7957
 7958    cx.assert_editor_state(indoc!(
 7959        r#"abc
 7960           def«ˇg»hi
 7961
 7962           jk
 7963           nlm«ˇo»
 7964           "#
 7965    ));
 7966
 7967    cx.update_editor(|editor, window, cx| {
 7968        editor.add_selection_below(&Default::default(), window, cx);
 7969    });
 7970
 7971    cx.assert_editor_state(indoc!(
 7972        r#"abc
 7973           def«ˇg»hi
 7974
 7975           jk
 7976           nlm«ˇo»
 7977           "#
 7978    ));
 7979
 7980    cx.update_editor(|editor, window, cx| {
 7981        editor.add_selection_above(&Default::default(), window, cx);
 7982    });
 7983
 7984    cx.assert_editor_state(indoc!(
 7985        r#"abc
 7986           def«ˇg»hi
 7987
 7988           jk
 7989           nlmo
 7990           "#
 7991    ));
 7992
 7993    cx.update_editor(|editor, window, cx| {
 7994        editor.add_selection_above(&Default::default(), window, cx);
 7995    });
 7996
 7997    cx.assert_editor_state(indoc!(
 7998        r#"abc
 7999           def«ˇg»hi
 8000
 8001           jk
 8002           nlmo
 8003           "#
 8004    ));
 8005
 8006    // Change selections again
 8007    cx.set_state(indoc!(
 8008        r#"a«bc
 8009           defgˇ»hi
 8010
 8011           jk
 8012           nlmo
 8013           "#
 8014    ));
 8015
 8016    cx.update_editor(|editor, window, cx| {
 8017        editor.add_selection_below(&Default::default(), window, cx);
 8018    });
 8019
 8020    cx.assert_editor_state(indoc!(
 8021        r#"a«bcˇ»
 8022           d«efgˇ»hi
 8023
 8024           j«kˇ»
 8025           nlmo
 8026           "#
 8027    ));
 8028
 8029    cx.update_editor(|editor, window, cx| {
 8030        editor.add_selection_below(&Default::default(), window, cx);
 8031    });
 8032    cx.assert_editor_state(indoc!(
 8033        r#"a«bcˇ»
 8034           d«efgˇ»hi
 8035
 8036           j«kˇ»
 8037           n«lmoˇ»
 8038           "#
 8039    ));
 8040    cx.update_editor(|editor, window, cx| {
 8041        editor.add_selection_above(&Default::default(), window, cx);
 8042    });
 8043
 8044    cx.assert_editor_state(indoc!(
 8045        r#"a«bcˇ»
 8046           d«efgˇ»hi
 8047
 8048           j«kˇ»
 8049           nlmo
 8050           "#
 8051    ));
 8052
 8053    // Change selections again
 8054    cx.set_state(indoc!(
 8055        r#"abc
 8056           d«ˇefghi
 8057
 8058           jk
 8059           nlm»o
 8060           "#
 8061    ));
 8062
 8063    cx.update_editor(|editor, window, cx| {
 8064        editor.add_selection_above(&Default::default(), window, cx);
 8065    });
 8066
 8067    cx.assert_editor_state(indoc!(
 8068        r#"a«ˇbc»
 8069           d«ˇef»ghi
 8070
 8071           j«ˇk»
 8072           n«ˇlm»o
 8073           "#
 8074    ));
 8075
 8076    cx.update_editor(|editor, window, cx| {
 8077        editor.add_selection_below(&Default::default(), window, cx);
 8078    });
 8079
 8080    cx.assert_editor_state(indoc!(
 8081        r#"abc
 8082           d«ˇef»ghi
 8083
 8084           j«ˇk»
 8085           n«ˇlm»o
 8086           "#
 8087    ));
 8088}
 8089
 8090#[gpui::test]
 8091async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8092    init_test(cx, |_| {});
 8093    let mut cx = EditorTestContext::new(cx).await;
 8094
 8095    cx.set_state(indoc!(
 8096        r#"line onˇe
 8097           liˇne two
 8098           line three
 8099           line four"#
 8100    ));
 8101
 8102    cx.update_editor(|editor, window, cx| {
 8103        editor.add_selection_below(&Default::default(), window, cx);
 8104    });
 8105
 8106    // test multiple cursors expand in the same direction
 8107    cx.assert_editor_state(indoc!(
 8108        r#"line onˇe
 8109           liˇne twˇo
 8110           liˇne three
 8111           line four"#
 8112    ));
 8113
 8114    cx.update_editor(|editor, window, cx| {
 8115        editor.add_selection_below(&Default::default(), window, cx);
 8116    });
 8117
 8118    cx.update_editor(|editor, window, cx| {
 8119        editor.add_selection_below(&Default::default(), window, cx);
 8120    });
 8121
 8122    // test multiple cursors expand below overflow
 8123    cx.assert_editor_state(indoc!(
 8124        r#"line onˇe
 8125           liˇne twˇo
 8126           liˇne thˇree
 8127           liˇne foˇur"#
 8128    ));
 8129
 8130    cx.update_editor(|editor, window, cx| {
 8131        editor.add_selection_above(&Default::default(), window, cx);
 8132    });
 8133
 8134    // test multiple cursors retrieves back correctly
 8135    cx.assert_editor_state(indoc!(
 8136        r#"line onˇe
 8137           liˇne twˇo
 8138           liˇne thˇree
 8139           line four"#
 8140    ));
 8141
 8142    cx.update_editor(|editor, window, cx| {
 8143        editor.add_selection_above(&Default::default(), window, cx);
 8144    });
 8145
 8146    cx.update_editor(|editor, window, cx| {
 8147        editor.add_selection_above(&Default::default(), window, cx);
 8148    });
 8149
 8150    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8151    cx.assert_editor_state(indoc!(
 8152        r#"liˇne onˇe
 8153           liˇne two
 8154           line three
 8155           line four"#
 8156    ));
 8157
 8158    cx.update_editor(|editor, window, cx| {
 8159        editor.undo_selection(&Default::default(), window, cx);
 8160    });
 8161
 8162    // test undo
 8163    cx.assert_editor_state(indoc!(
 8164        r#"line onˇe
 8165           liˇne twˇo
 8166           line three
 8167           line four"#
 8168    ));
 8169
 8170    cx.update_editor(|editor, window, cx| {
 8171        editor.redo_selection(&Default::default(), window, cx);
 8172    });
 8173
 8174    // test redo
 8175    cx.assert_editor_state(indoc!(
 8176        r#"liˇne onˇe
 8177           liˇne two
 8178           line three
 8179           line four"#
 8180    ));
 8181
 8182    cx.set_state(indoc!(
 8183        r#"abcd
 8184           ef«ghˇ»
 8185           ijkl
 8186           «mˇ»nop"#
 8187    ));
 8188
 8189    cx.update_editor(|editor, window, cx| {
 8190        editor.add_selection_above(&Default::default(), window, cx);
 8191    });
 8192
 8193    // test multiple selections expand in the same direction
 8194    cx.assert_editor_state(indoc!(
 8195        r#"ab«cdˇ»
 8196           ef«ghˇ»
 8197           «iˇ»jkl
 8198           «mˇ»nop"#
 8199    ));
 8200
 8201    cx.update_editor(|editor, window, cx| {
 8202        editor.add_selection_above(&Default::default(), window, cx);
 8203    });
 8204
 8205    // test multiple selection upward overflow
 8206    cx.assert_editor_state(indoc!(
 8207        r#"ab«cdˇ»
 8208           «eˇ»f«ghˇ»
 8209           «iˇ»jkl
 8210           «mˇ»nop"#
 8211    ));
 8212
 8213    cx.update_editor(|editor, window, cx| {
 8214        editor.add_selection_below(&Default::default(), window, cx);
 8215    });
 8216
 8217    // test multiple selection retrieves back correctly
 8218    cx.assert_editor_state(indoc!(
 8219        r#"abcd
 8220           ef«ghˇ»
 8221           «iˇ»jkl
 8222           «mˇ»nop"#
 8223    ));
 8224
 8225    cx.update_editor(|editor, window, cx| {
 8226        editor.add_selection_below(&Default::default(), window, cx);
 8227    });
 8228
 8229    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8230    cx.assert_editor_state(indoc!(
 8231        r#"abcd
 8232           ef«ghˇ»
 8233           ij«klˇ»
 8234           «mˇ»nop"#
 8235    ));
 8236
 8237    cx.update_editor(|editor, window, cx| {
 8238        editor.undo_selection(&Default::default(), window, cx);
 8239    });
 8240
 8241    // test undo
 8242    cx.assert_editor_state(indoc!(
 8243        r#"abcd
 8244           ef«ghˇ»
 8245           «iˇ»jkl
 8246           «mˇ»nop"#
 8247    ));
 8248
 8249    cx.update_editor(|editor, window, cx| {
 8250        editor.redo_selection(&Default::default(), window, cx);
 8251    });
 8252
 8253    // test redo
 8254    cx.assert_editor_state(indoc!(
 8255        r#"abcd
 8256           ef«ghˇ»
 8257           ij«klˇ»
 8258           «mˇ»nop"#
 8259    ));
 8260}
 8261
 8262#[gpui::test]
 8263async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8264    init_test(cx, |_| {});
 8265    let mut cx = EditorTestContext::new(cx).await;
 8266
 8267    cx.set_state(indoc!(
 8268        r#"line onˇe
 8269           liˇne two
 8270           line three
 8271           line four"#
 8272    ));
 8273
 8274    cx.update_editor(|editor, window, cx| {
 8275        editor.add_selection_below(&Default::default(), window, cx);
 8276        editor.add_selection_below(&Default::default(), window, cx);
 8277        editor.add_selection_below(&Default::default(), window, cx);
 8278    });
 8279
 8280    // initial state with two multi cursor groups
 8281    cx.assert_editor_state(indoc!(
 8282        r#"line onˇe
 8283           liˇne twˇo
 8284           liˇne thˇree
 8285           liˇne foˇur"#
 8286    ));
 8287
 8288    // add single cursor in middle - simulate opt click
 8289    cx.update_editor(|editor, window, cx| {
 8290        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8291        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8292        editor.end_selection(window, cx);
 8293    });
 8294
 8295    cx.assert_editor_state(indoc!(
 8296        r#"line onˇe
 8297           liˇne twˇo
 8298           liˇneˇ thˇree
 8299           liˇne foˇur"#
 8300    ));
 8301
 8302    cx.update_editor(|editor, window, cx| {
 8303        editor.add_selection_above(&Default::default(), window, cx);
 8304    });
 8305
 8306    // test new added selection expands above and existing selection shrinks
 8307    cx.assert_editor_state(indoc!(
 8308        r#"line onˇe
 8309           liˇneˇ twˇo
 8310           liˇneˇ thˇree
 8311           line four"#
 8312    ));
 8313
 8314    cx.update_editor(|editor, window, cx| {
 8315        editor.add_selection_above(&Default::default(), window, cx);
 8316    });
 8317
 8318    // test new added selection expands above and existing selection shrinks
 8319    cx.assert_editor_state(indoc!(
 8320        r#"lineˇ onˇe
 8321           liˇneˇ twˇo
 8322           lineˇ three
 8323           line four"#
 8324    ));
 8325
 8326    // intial state with two selection groups
 8327    cx.set_state(indoc!(
 8328        r#"abcd
 8329           ef«ghˇ»
 8330           ijkl
 8331           «mˇ»nop"#
 8332    ));
 8333
 8334    cx.update_editor(|editor, window, cx| {
 8335        editor.add_selection_above(&Default::default(), window, cx);
 8336        editor.add_selection_above(&Default::default(), window, cx);
 8337    });
 8338
 8339    cx.assert_editor_state(indoc!(
 8340        r#"ab«cdˇ»
 8341           «eˇ»f«ghˇ»
 8342           «iˇ»jkl
 8343           «mˇ»nop"#
 8344    ));
 8345
 8346    // add single selection in middle - simulate opt drag
 8347    cx.update_editor(|editor, window, cx| {
 8348        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8349        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8350        editor.update_selection(
 8351            DisplayPoint::new(DisplayRow(2), 4),
 8352            0,
 8353            gpui::Point::<f32>::default(),
 8354            window,
 8355            cx,
 8356        );
 8357        editor.end_selection(window, cx);
 8358    });
 8359
 8360    cx.assert_editor_state(indoc!(
 8361        r#"ab«cdˇ»
 8362           «eˇ»f«ghˇ»
 8363           «iˇ»jk«lˇ»
 8364           «mˇ»nop"#
 8365    ));
 8366
 8367    cx.update_editor(|editor, window, cx| {
 8368        editor.add_selection_below(&Default::default(), window, cx);
 8369    });
 8370
 8371    // test new added selection expands below, others shrinks from above
 8372    cx.assert_editor_state(indoc!(
 8373        r#"abcd
 8374           ef«ghˇ»
 8375           «iˇ»jk«lˇ»
 8376           «mˇ»no«pˇ»"#
 8377    ));
 8378}
 8379
 8380#[gpui::test]
 8381async fn test_select_next(cx: &mut TestAppContext) {
 8382    init_test(cx, |_| {});
 8383    let mut cx = EditorTestContext::new(cx).await;
 8384
 8385    // Enable case sensitive search.
 8386    update_test_editor_settings(&mut cx, |settings| {
 8387        let mut search_settings = SearchSettingsContent::default();
 8388        search_settings.case_sensitive = Some(true);
 8389        settings.search = Some(search_settings);
 8390    });
 8391
 8392    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8393
 8394    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8395        .unwrap();
 8396    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8397
 8398    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8399        .unwrap();
 8400    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8401
 8402    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8403    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8404
 8405    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8406    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8407
 8408    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8409        .unwrap();
 8410    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8411
 8412    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8413        .unwrap();
 8414    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8415
 8416    // Test selection direction should be preserved
 8417    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8418
 8419    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8420        .unwrap();
 8421    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8422
 8423    // Test case sensitivity
 8424    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8425    cx.update_editor(|e, window, cx| {
 8426        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8427    });
 8428    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8429
 8430    // Disable case sensitive search.
 8431    update_test_editor_settings(&mut cx, |settings| {
 8432        let mut search_settings = SearchSettingsContent::default();
 8433        search_settings.case_sensitive = Some(false);
 8434        settings.search = Some(search_settings);
 8435    });
 8436
 8437    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8438    cx.update_editor(|e, window, cx| {
 8439        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8440        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8441    });
 8442    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8443}
 8444
 8445#[gpui::test]
 8446async fn test_select_all_matches(cx: &mut TestAppContext) {
 8447    init_test(cx, |_| {});
 8448    let mut cx = EditorTestContext::new(cx).await;
 8449
 8450    // Enable case sensitive search.
 8451    update_test_editor_settings(&mut cx, |settings| {
 8452        let mut search_settings = SearchSettingsContent::default();
 8453        search_settings.case_sensitive = Some(true);
 8454        settings.search = Some(search_settings);
 8455    });
 8456
 8457    // Test caret-only selections
 8458    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8459    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8460        .unwrap();
 8461    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8462
 8463    // Test left-to-right selections
 8464    cx.set_state("abc\n«abcˇ»\nabc");
 8465    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8466        .unwrap();
 8467    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8468
 8469    // Test right-to-left selections
 8470    cx.set_state("abc\n«ˇabc»\nabc");
 8471    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8472        .unwrap();
 8473    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8474
 8475    // Test selecting whitespace with caret selection
 8476    cx.set_state("abc\nˇ   abc\nabc");
 8477    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8478        .unwrap();
 8479    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8480
 8481    // Test selecting whitespace with left-to-right selection
 8482    cx.set_state("abc\n«ˇ  »abc\nabc");
 8483    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8484        .unwrap();
 8485    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8486
 8487    // Test no matches with right-to-left selection
 8488    cx.set_state("abc\n«  ˇ»abc\nabc");
 8489    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8490        .unwrap();
 8491    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8492
 8493    // Test with a single word and clip_at_line_ends=true (#29823)
 8494    cx.set_state("aˇbc");
 8495    cx.update_editor(|e, window, cx| {
 8496        e.set_clip_at_line_ends(true, cx);
 8497        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8498        e.set_clip_at_line_ends(false, cx);
 8499    });
 8500    cx.assert_editor_state("«abcˇ»");
 8501
 8502    // Test case sensitivity
 8503    cx.set_state("fˇoo\nFOO\nFoo");
 8504    cx.update_editor(|e, window, cx| {
 8505        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8506    });
 8507    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8508
 8509    // Disable case sensitive search.
 8510    update_test_editor_settings(&mut cx, |settings| {
 8511        let mut search_settings = SearchSettingsContent::default();
 8512        search_settings.case_sensitive = Some(false);
 8513        settings.search = Some(search_settings);
 8514    });
 8515
 8516    cx.set_state("fˇoo\nFOO\nFoo");
 8517    cx.update_editor(|e, window, cx| {
 8518        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8519    });
 8520    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8521}
 8522
 8523#[gpui::test]
 8524async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8525    init_test(cx, |_| {});
 8526
 8527    let mut cx = EditorTestContext::new(cx).await;
 8528
 8529    let large_body_1 = "\nd".repeat(200);
 8530    let large_body_2 = "\ne".repeat(200);
 8531
 8532    cx.set_state(&format!(
 8533        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8534    ));
 8535    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8536        let scroll_position = editor.scroll_position(cx);
 8537        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8538        scroll_position
 8539    });
 8540
 8541    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8542        .unwrap();
 8543    cx.assert_editor_state(&format!(
 8544        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8545    ));
 8546    let scroll_position_after_selection =
 8547        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8548    assert_eq!(
 8549        initial_scroll_position, scroll_position_after_selection,
 8550        "Scroll position should not change after selecting all matches"
 8551    );
 8552}
 8553
 8554#[gpui::test]
 8555async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8556    init_test(cx, |_| {});
 8557
 8558    let mut cx = EditorLspTestContext::new_rust(
 8559        lsp::ServerCapabilities {
 8560            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8561            ..Default::default()
 8562        },
 8563        cx,
 8564    )
 8565    .await;
 8566
 8567    cx.set_state(indoc! {"
 8568        line 1
 8569        line 2
 8570        linˇe 3
 8571        line 4
 8572        line 5
 8573    "});
 8574
 8575    // Make an edit
 8576    cx.update_editor(|editor, window, cx| {
 8577        editor.handle_input("X", window, cx);
 8578    });
 8579
 8580    // Move cursor to a different position
 8581    cx.update_editor(|editor, window, cx| {
 8582        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8583            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8584        });
 8585    });
 8586
 8587    cx.assert_editor_state(indoc! {"
 8588        line 1
 8589        line 2
 8590        linXe 3
 8591        line 4
 8592        liˇne 5
 8593    "});
 8594
 8595    cx.lsp
 8596        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8597            Ok(Some(vec![lsp::TextEdit::new(
 8598                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8599                "PREFIX ".to_string(),
 8600            )]))
 8601        });
 8602
 8603    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8604        .unwrap()
 8605        .await
 8606        .unwrap();
 8607
 8608    cx.assert_editor_state(indoc! {"
 8609        PREFIX line 1
 8610        line 2
 8611        linXe 3
 8612        line 4
 8613        liˇne 5
 8614    "});
 8615
 8616    // Undo formatting
 8617    cx.update_editor(|editor, window, cx| {
 8618        editor.undo(&Default::default(), window, cx);
 8619    });
 8620
 8621    // Verify cursor moved back to position after edit
 8622    cx.assert_editor_state(indoc! {"
 8623        line 1
 8624        line 2
 8625        linXˇe 3
 8626        line 4
 8627        line 5
 8628    "});
 8629}
 8630
 8631#[gpui::test]
 8632async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8633    init_test(cx, |_| {});
 8634
 8635    let mut cx = EditorTestContext::new(cx).await;
 8636
 8637    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8638    cx.update_editor(|editor, window, cx| {
 8639        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8640    });
 8641
 8642    cx.set_state(indoc! {"
 8643        line 1
 8644        line 2
 8645        linˇe 3
 8646        line 4
 8647        line 5
 8648        line 6
 8649        line 7
 8650        line 8
 8651        line 9
 8652        line 10
 8653    "});
 8654
 8655    let snapshot = cx.buffer_snapshot();
 8656    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8657
 8658    cx.update(|_, cx| {
 8659        provider.update(cx, |provider, _| {
 8660            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8661                id: None,
 8662                edits: vec![(edit_position..edit_position, "X".into())],
 8663                edit_preview: None,
 8664            }))
 8665        })
 8666    });
 8667
 8668    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8669    cx.update_editor(|editor, window, cx| {
 8670        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8671    });
 8672
 8673    cx.assert_editor_state(indoc! {"
 8674        line 1
 8675        line 2
 8676        lineXˇ 3
 8677        line 4
 8678        line 5
 8679        line 6
 8680        line 7
 8681        line 8
 8682        line 9
 8683        line 10
 8684    "});
 8685
 8686    cx.update_editor(|editor, window, cx| {
 8687        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8688            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8689        });
 8690    });
 8691
 8692    cx.assert_editor_state(indoc! {"
 8693        line 1
 8694        line 2
 8695        lineX 3
 8696        line 4
 8697        line 5
 8698        line 6
 8699        line 7
 8700        line 8
 8701        line 9
 8702        liˇne 10
 8703    "});
 8704
 8705    cx.update_editor(|editor, window, cx| {
 8706        editor.undo(&Default::default(), window, cx);
 8707    });
 8708
 8709    cx.assert_editor_state(indoc! {"
 8710        line 1
 8711        line 2
 8712        lineˇ 3
 8713        line 4
 8714        line 5
 8715        line 6
 8716        line 7
 8717        line 8
 8718        line 9
 8719        line 10
 8720    "});
 8721}
 8722
 8723#[gpui::test]
 8724async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8725    init_test(cx, |_| {});
 8726
 8727    let mut cx = EditorTestContext::new(cx).await;
 8728    cx.set_state(
 8729        r#"let foo = 2;
 8730lˇet foo = 2;
 8731let fooˇ = 2;
 8732let foo = 2;
 8733let foo = ˇ2;"#,
 8734    );
 8735
 8736    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8737        .unwrap();
 8738    cx.assert_editor_state(
 8739        r#"let foo = 2;
 8740«letˇ» foo = 2;
 8741let «fooˇ» = 2;
 8742let foo = 2;
 8743let foo = «2ˇ»;"#,
 8744    );
 8745
 8746    // noop for multiple selections with different contents
 8747    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8748        .unwrap();
 8749    cx.assert_editor_state(
 8750        r#"let foo = 2;
 8751«letˇ» foo = 2;
 8752let «fooˇ» = 2;
 8753let foo = 2;
 8754let foo = «2ˇ»;"#,
 8755    );
 8756
 8757    // Test last selection direction should be preserved
 8758    cx.set_state(
 8759        r#"let foo = 2;
 8760let foo = 2;
 8761let «fooˇ» = 2;
 8762let «ˇfoo» = 2;
 8763let foo = 2;"#,
 8764    );
 8765
 8766    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8767        .unwrap();
 8768    cx.assert_editor_state(
 8769        r#"let foo = 2;
 8770let foo = 2;
 8771let «fooˇ» = 2;
 8772let «ˇfoo» = 2;
 8773let «ˇfoo» = 2;"#,
 8774    );
 8775}
 8776
 8777#[gpui::test]
 8778async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8779    init_test(cx, |_| {});
 8780
 8781    let mut cx =
 8782        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8783
 8784    cx.assert_editor_state(indoc! {"
 8785        ˇbbb
 8786        ccc
 8787
 8788        bbb
 8789        ccc
 8790        "});
 8791    cx.dispatch_action(SelectPrevious::default());
 8792    cx.assert_editor_state(indoc! {"
 8793                «bbbˇ»
 8794                ccc
 8795
 8796                bbb
 8797                ccc
 8798                "});
 8799    cx.dispatch_action(SelectPrevious::default());
 8800    cx.assert_editor_state(indoc! {"
 8801                «bbbˇ»
 8802                ccc
 8803
 8804                «bbbˇ»
 8805                ccc
 8806                "});
 8807}
 8808
 8809#[gpui::test]
 8810async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8811    init_test(cx, |_| {});
 8812
 8813    let mut cx = EditorTestContext::new(cx).await;
 8814    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8815
 8816    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8817        .unwrap();
 8818    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8819
 8820    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8821        .unwrap();
 8822    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8823
 8824    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8825    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8826
 8827    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8828    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8829
 8830    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8831        .unwrap();
 8832    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8833
 8834    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8835        .unwrap();
 8836    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8837}
 8838
 8839#[gpui::test]
 8840async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8841    init_test(cx, |_| {});
 8842
 8843    let mut cx = EditorTestContext::new(cx).await;
 8844    cx.set_state("");
 8845
 8846    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8847        .unwrap();
 8848    cx.assert_editor_state("«aˇ»");
 8849    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8850        .unwrap();
 8851    cx.assert_editor_state("«aˇ»");
 8852}
 8853
 8854#[gpui::test]
 8855async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8856    init_test(cx, |_| {});
 8857
 8858    let mut cx = EditorTestContext::new(cx).await;
 8859    cx.set_state(
 8860        r#"let foo = 2;
 8861lˇet foo = 2;
 8862let fooˇ = 2;
 8863let foo = 2;
 8864let foo = ˇ2;"#,
 8865    );
 8866
 8867    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8868        .unwrap();
 8869    cx.assert_editor_state(
 8870        r#"let foo = 2;
 8871«letˇ» foo = 2;
 8872let «fooˇ» = 2;
 8873let foo = 2;
 8874let foo = «2ˇ»;"#,
 8875    );
 8876
 8877    // noop for multiple selections with different contents
 8878    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8879        .unwrap();
 8880    cx.assert_editor_state(
 8881        r#"let foo = 2;
 8882«letˇ» foo = 2;
 8883let «fooˇ» = 2;
 8884let foo = 2;
 8885let foo = «2ˇ»;"#,
 8886    );
 8887}
 8888
 8889#[gpui::test]
 8890async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8891    init_test(cx, |_| {});
 8892    let mut cx = EditorTestContext::new(cx).await;
 8893
 8894    // Enable case sensitive search.
 8895    update_test_editor_settings(&mut cx, |settings| {
 8896        let mut search_settings = SearchSettingsContent::default();
 8897        search_settings.case_sensitive = Some(true);
 8898        settings.search = Some(search_settings);
 8899    });
 8900
 8901    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8902
 8903    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8904        .unwrap();
 8905    // selection direction is preserved
 8906    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8907
 8908    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8909        .unwrap();
 8910    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8911
 8912    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8913    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8914
 8915    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8916    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8917
 8918    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8919        .unwrap();
 8920    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8921
 8922    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8923        .unwrap();
 8924    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8925
 8926    // Test case sensitivity
 8927    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 8928    cx.update_editor(|e, window, cx| {
 8929        e.select_previous(&SelectPrevious::default(), window, cx)
 8930            .unwrap();
 8931        e.select_previous(&SelectPrevious::default(), window, cx)
 8932            .unwrap();
 8933    });
 8934    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8935
 8936    // Disable case sensitive search.
 8937    update_test_editor_settings(&mut cx, |settings| {
 8938        let mut search_settings = SearchSettingsContent::default();
 8939        search_settings.case_sensitive = Some(false);
 8940        settings.search = Some(search_settings);
 8941    });
 8942
 8943    cx.set_state("foo\nFOO\n«ˇFoo»");
 8944    cx.update_editor(|e, window, cx| {
 8945        e.select_previous(&SelectPrevious::default(), window, cx)
 8946            .unwrap();
 8947        e.select_previous(&SelectPrevious::default(), window, cx)
 8948            .unwrap();
 8949    });
 8950    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8951}
 8952
 8953#[gpui::test]
 8954async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8955    init_test(cx, |_| {});
 8956
 8957    let language = Arc::new(Language::new(
 8958        LanguageConfig::default(),
 8959        Some(tree_sitter_rust::LANGUAGE.into()),
 8960    ));
 8961
 8962    let text = r#"
 8963        use mod1::mod2::{mod3, mod4};
 8964
 8965        fn fn_1(param1: bool, param2: &str) {
 8966            let var1 = "text";
 8967        }
 8968    "#
 8969    .unindent();
 8970
 8971    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8972    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8973    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8974
 8975    editor
 8976        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8977        .await;
 8978
 8979    editor.update_in(cx, |editor, window, cx| {
 8980        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8981            s.select_display_ranges([
 8982                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8983                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8984                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8985            ]);
 8986        });
 8987        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8988    });
 8989    editor.update(cx, |editor, cx| {
 8990        assert_text_with_selections(
 8991            editor,
 8992            indoc! {r#"
 8993                use mod1::mod2::{mod3, «mod4ˇ»};
 8994
 8995                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8996                    let var1 = "«ˇtext»";
 8997                }
 8998            "#},
 8999            cx,
 9000        );
 9001    });
 9002
 9003    editor.update_in(cx, |editor, window, cx| {
 9004        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9005    });
 9006    editor.update(cx, |editor, cx| {
 9007        assert_text_with_selections(
 9008            editor,
 9009            indoc! {r#"
 9010                use mod1::mod2::«{mod3, mod4}ˇ»;
 9011
 9012                «ˇfn fn_1(param1: bool, param2: &str) {
 9013                    let var1 = "text";
 9014 9015            "#},
 9016            cx,
 9017        );
 9018    });
 9019
 9020    editor.update_in(cx, |editor, window, cx| {
 9021        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9022    });
 9023    assert_eq!(
 9024        editor.update(cx, |editor, cx| editor
 9025            .selections
 9026            .display_ranges(&editor.display_snapshot(cx))),
 9027        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9028    );
 9029
 9030    // Trying to expand the selected syntax node one more time has no effect.
 9031    editor.update_in(cx, |editor, window, cx| {
 9032        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9033    });
 9034    assert_eq!(
 9035        editor.update(cx, |editor, cx| editor
 9036            .selections
 9037            .display_ranges(&editor.display_snapshot(cx))),
 9038        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9039    );
 9040
 9041    editor.update_in(cx, |editor, window, cx| {
 9042        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9043    });
 9044    editor.update(cx, |editor, cx| {
 9045        assert_text_with_selections(
 9046            editor,
 9047            indoc! {r#"
 9048                use mod1::mod2::«{mod3, mod4}ˇ»;
 9049
 9050                «ˇfn fn_1(param1: bool, param2: &str) {
 9051                    let var1 = "text";
 9052 9053            "#},
 9054            cx,
 9055        );
 9056    });
 9057
 9058    editor.update_in(cx, |editor, window, cx| {
 9059        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9060    });
 9061    editor.update(cx, |editor, cx| {
 9062        assert_text_with_selections(
 9063            editor,
 9064            indoc! {r#"
 9065                use mod1::mod2::{mod3, «mod4ˇ»};
 9066
 9067                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9068                    let var1 = "«ˇtext»";
 9069                }
 9070            "#},
 9071            cx,
 9072        );
 9073    });
 9074
 9075    editor.update_in(cx, |editor, window, cx| {
 9076        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9077    });
 9078    editor.update(cx, |editor, cx| {
 9079        assert_text_with_selections(
 9080            editor,
 9081            indoc! {r#"
 9082                use mod1::mod2::{mod3, moˇd4};
 9083
 9084                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9085                    let var1 = "teˇxt";
 9086                }
 9087            "#},
 9088            cx,
 9089        );
 9090    });
 9091
 9092    // Trying to shrink the selected syntax node one more time has no effect.
 9093    editor.update_in(cx, |editor, window, cx| {
 9094        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9095    });
 9096    editor.update_in(cx, |editor, _, cx| {
 9097        assert_text_with_selections(
 9098            editor,
 9099            indoc! {r#"
 9100                use mod1::mod2::{mod3, moˇd4};
 9101
 9102                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9103                    let var1 = "teˇxt";
 9104                }
 9105            "#},
 9106            cx,
 9107        );
 9108    });
 9109
 9110    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9111    // a fold.
 9112    editor.update_in(cx, |editor, window, cx| {
 9113        editor.fold_creases(
 9114            vec![
 9115                Crease::simple(
 9116                    Point::new(0, 21)..Point::new(0, 24),
 9117                    FoldPlaceholder::test(),
 9118                ),
 9119                Crease::simple(
 9120                    Point::new(3, 20)..Point::new(3, 22),
 9121                    FoldPlaceholder::test(),
 9122                ),
 9123            ],
 9124            true,
 9125            window,
 9126            cx,
 9127        );
 9128        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9129    });
 9130    editor.update(cx, |editor, cx| {
 9131        assert_text_with_selections(
 9132            editor,
 9133            indoc! {r#"
 9134                use mod1::mod2::«{mod3, mod4}ˇ»;
 9135
 9136                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9137                    let var1 = "«ˇtext»";
 9138                }
 9139            "#},
 9140            cx,
 9141        );
 9142    });
 9143}
 9144
 9145#[gpui::test]
 9146async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9147    init_test(cx, |_| {});
 9148
 9149    let language = Arc::new(Language::new(
 9150        LanguageConfig::default(),
 9151        Some(tree_sitter_rust::LANGUAGE.into()),
 9152    ));
 9153
 9154    let text = "let a = 2;";
 9155
 9156    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9157    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9158    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9159
 9160    editor
 9161        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9162        .await;
 9163
 9164    // Test case 1: Cursor at end of word
 9165    editor.update_in(cx, |editor, window, cx| {
 9166        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9167            s.select_display_ranges([
 9168                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9169            ]);
 9170        });
 9171    });
 9172    editor.update(cx, |editor, cx| {
 9173        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9174    });
 9175    editor.update_in(cx, |editor, window, cx| {
 9176        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9177    });
 9178    editor.update(cx, |editor, cx| {
 9179        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 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(editor, "«ˇlet a = 2;»", cx);
 9186    });
 9187
 9188    // Test case 2: Cursor at end of statement
 9189    editor.update_in(cx, |editor, window, cx| {
 9190        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9191            s.select_display_ranges([
 9192                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9193            ]);
 9194        });
 9195    });
 9196    editor.update(cx, |editor, cx| {
 9197        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9198    });
 9199    editor.update_in(cx, |editor, window, cx| {
 9200        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9201    });
 9202    editor.update(cx, |editor, cx| {
 9203        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9204    });
 9205}
 9206
 9207#[gpui::test]
 9208async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9209    init_test(cx, |_| {});
 9210
 9211    let language = Arc::new(Language::new(
 9212        LanguageConfig {
 9213            name: "JavaScript".into(),
 9214            ..Default::default()
 9215        },
 9216        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9217    ));
 9218
 9219    let text = r#"
 9220        let a = {
 9221            key: "value",
 9222        };
 9223    "#
 9224    .unindent();
 9225
 9226    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9227    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9228    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9229
 9230    editor
 9231        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9232        .await;
 9233
 9234    // Test case 1: Cursor after '{'
 9235    editor.update_in(cx, |editor, window, cx| {
 9236        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9237            s.select_display_ranges([
 9238                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9239            ]);
 9240        });
 9241    });
 9242    editor.update(cx, |editor, cx| {
 9243        assert_text_with_selections(
 9244            editor,
 9245            indoc! {r#"
 9246                let a = {ˇ
 9247                    key: "value",
 9248                };
 9249            "#},
 9250            cx,
 9251        );
 9252    });
 9253    editor.update_in(cx, |editor, window, cx| {
 9254        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9255    });
 9256    editor.update(cx, |editor, cx| {
 9257        assert_text_with_selections(
 9258            editor,
 9259            indoc! {r#"
 9260                let a = «ˇ{
 9261                    key: "value",
 9262                }»;
 9263            "#},
 9264            cx,
 9265        );
 9266    });
 9267
 9268    // Test case 2: Cursor after ':'
 9269    editor.update_in(cx, |editor, window, cx| {
 9270        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9271            s.select_display_ranges([
 9272                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9273            ]);
 9274        });
 9275    });
 9276    editor.update(cx, |editor, cx| {
 9277        assert_text_with_selections(
 9278            editor,
 9279            indoc! {r#"
 9280                let a = {
 9281                    key:ˇ "value",
 9282                };
 9283            "#},
 9284            cx,
 9285        );
 9286    });
 9287    editor.update_in(cx, |editor, window, cx| {
 9288        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9289    });
 9290    editor.update(cx, |editor, cx| {
 9291        assert_text_with_selections(
 9292            editor,
 9293            indoc! {r#"
 9294                let a = {
 9295                    «ˇkey: "value"»,
 9296                };
 9297            "#},
 9298            cx,
 9299        );
 9300    });
 9301    editor.update_in(cx, |editor, window, cx| {
 9302        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9303    });
 9304    editor.update(cx, |editor, cx| {
 9305        assert_text_with_selections(
 9306            editor,
 9307            indoc! {r#"
 9308                let a = «ˇ{
 9309                    key: "value",
 9310                }»;
 9311            "#},
 9312            cx,
 9313        );
 9314    });
 9315
 9316    // Test case 3: Cursor after ','
 9317    editor.update_in(cx, |editor, window, cx| {
 9318        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9319            s.select_display_ranges([
 9320                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9321            ]);
 9322        });
 9323    });
 9324    editor.update(cx, |editor, cx| {
 9325        assert_text_with_selections(
 9326            editor,
 9327            indoc! {r#"
 9328                let a = {
 9329                    key: "value",ˇ
 9330                };
 9331            "#},
 9332            cx,
 9333        );
 9334    });
 9335    editor.update_in(cx, |editor, window, cx| {
 9336        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9337    });
 9338    editor.update(cx, |editor, cx| {
 9339        assert_text_with_selections(
 9340            editor,
 9341            indoc! {r#"
 9342                let a = «ˇ{
 9343                    key: "value",
 9344                }»;
 9345            "#},
 9346            cx,
 9347        );
 9348    });
 9349
 9350    // Test case 4: Cursor after ';'
 9351    editor.update_in(cx, |editor, window, cx| {
 9352        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9353            s.select_display_ranges([
 9354                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9355            ]);
 9356        });
 9357    });
 9358    editor.update(cx, |editor, cx| {
 9359        assert_text_with_selections(
 9360            editor,
 9361            indoc! {r#"
 9362                let a = {
 9363                    key: "value",
 9364                };ˇ
 9365            "#},
 9366            cx,
 9367        );
 9368    });
 9369    editor.update_in(cx, |editor, window, cx| {
 9370        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9371    });
 9372    editor.update(cx, |editor, cx| {
 9373        assert_text_with_selections(
 9374            editor,
 9375            indoc! {r#"
 9376                «ˇlet a = {
 9377                    key: "value",
 9378                };
 9379                »"#},
 9380            cx,
 9381        );
 9382    });
 9383}
 9384
 9385#[gpui::test]
 9386async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9387    init_test(cx, |_| {});
 9388
 9389    let language = Arc::new(Language::new(
 9390        LanguageConfig::default(),
 9391        Some(tree_sitter_rust::LANGUAGE.into()),
 9392    ));
 9393
 9394    let text = r#"
 9395        use mod1::mod2::{mod3, mod4};
 9396
 9397        fn fn_1(param1: bool, param2: &str) {
 9398            let var1 = "hello world";
 9399        }
 9400    "#
 9401    .unindent();
 9402
 9403    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9404    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9405    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9406
 9407    editor
 9408        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9409        .await;
 9410
 9411    // Test 1: Cursor on a letter of a string word
 9412    editor.update_in(cx, |editor, window, cx| {
 9413        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9414            s.select_display_ranges([
 9415                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9416            ]);
 9417        });
 9418    });
 9419    editor.update_in(cx, |editor, window, cx| {
 9420        assert_text_with_selections(
 9421            editor,
 9422            indoc! {r#"
 9423                use mod1::mod2::{mod3, mod4};
 9424
 9425                fn fn_1(param1: bool, param2: &str) {
 9426                    let var1 = "hˇello world";
 9427                }
 9428            "#},
 9429            cx,
 9430        );
 9431        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9432        assert_text_with_selections(
 9433            editor,
 9434            indoc! {r#"
 9435                use mod1::mod2::{mod3, mod4};
 9436
 9437                fn fn_1(param1: bool, param2: &str) {
 9438                    let var1 = "«ˇhello» world";
 9439                }
 9440            "#},
 9441            cx,
 9442        );
 9443    });
 9444
 9445    // Test 2: Partial selection within a word
 9446    editor.update_in(cx, |editor, window, cx| {
 9447        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9448            s.select_display_ranges([
 9449                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9450            ]);
 9451        });
 9452    });
 9453    editor.update_in(cx, |editor, window, cx| {
 9454        assert_text_with_selections(
 9455            editor,
 9456            indoc! {r#"
 9457                use mod1::mod2::{mod3, mod4};
 9458
 9459                fn fn_1(param1: bool, param2: &str) {
 9460                    let var1 = "h«elˇ»lo world";
 9461                }
 9462            "#},
 9463            cx,
 9464        );
 9465        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9466        assert_text_with_selections(
 9467            editor,
 9468            indoc! {r#"
 9469                use mod1::mod2::{mod3, mod4};
 9470
 9471                fn fn_1(param1: bool, param2: &str) {
 9472                    let var1 = "«ˇhello» world";
 9473                }
 9474            "#},
 9475            cx,
 9476        );
 9477    });
 9478
 9479    // Test 3: Complete word already selected
 9480    editor.update_in(cx, |editor, window, cx| {
 9481        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9482            s.select_display_ranges([
 9483                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9484            ]);
 9485        });
 9486    });
 9487    editor.update_in(cx, |editor, window, cx| {
 9488        assert_text_with_selections(
 9489            editor,
 9490            indoc! {r#"
 9491                use mod1::mod2::{mod3, mod4};
 9492
 9493                fn fn_1(param1: bool, param2: &str) {
 9494                    let var1 = "«helloˇ» world";
 9495                }
 9496            "#},
 9497            cx,
 9498        );
 9499        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9500        assert_text_with_selections(
 9501            editor,
 9502            indoc! {r#"
 9503                use mod1::mod2::{mod3, mod4};
 9504
 9505                fn fn_1(param1: bool, param2: &str) {
 9506                    let var1 = "«hello worldˇ»";
 9507                }
 9508            "#},
 9509            cx,
 9510        );
 9511    });
 9512
 9513    // Test 4: Selection spanning across words
 9514    editor.update_in(cx, |editor, window, cx| {
 9515        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9516            s.select_display_ranges([
 9517                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9518            ]);
 9519        });
 9520    });
 9521    editor.update_in(cx, |editor, window, cx| {
 9522        assert_text_with_selections(
 9523            editor,
 9524            indoc! {r#"
 9525                use mod1::mod2::{mod3, mod4};
 9526
 9527                fn fn_1(param1: bool, param2: &str) {
 9528                    let var1 = "hel«lo woˇ»rld";
 9529                }
 9530            "#},
 9531            cx,
 9532        );
 9533        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9534        assert_text_with_selections(
 9535            editor,
 9536            indoc! {r#"
 9537                use mod1::mod2::{mod3, mod4};
 9538
 9539                fn fn_1(param1: bool, param2: &str) {
 9540                    let var1 = "«ˇhello world»";
 9541                }
 9542            "#},
 9543            cx,
 9544        );
 9545    });
 9546
 9547    // Test 5: Expansion beyond string
 9548    editor.update_in(cx, |editor, window, cx| {
 9549        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9550        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9551        assert_text_with_selections(
 9552            editor,
 9553            indoc! {r#"
 9554                use mod1::mod2::{mod3, mod4};
 9555
 9556                fn fn_1(param1: bool, param2: &str) {
 9557                    «ˇlet var1 = "hello world";»
 9558                }
 9559            "#},
 9560            cx,
 9561        );
 9562    });
 9563}
 9564
 9565#[gpui::test]
 9566async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9567    init_test(cx, |_| {});
 9568
 9569    let mut cx = EditorTestContext::new(cx).await;
 9570
 9571    let language = Arc::new(Language::new(
 9572        LanguageConfig::default(),
 9573        Some(tree_sitter_rust::LANGUAGE.into()),
 9574    ));
 9575
 9576    cx.update_buffer(|buffer, cx| {
 9577        buffer.set_language(Some(language), cx);
 9578    });
 9579
 9580    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9581    cx.update_editor(|editor, window, cx| {
 9582        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9583    });
 9584
 9585    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9586
 9587    cx.set_state(indoc! { r#"fn a() {
 9588          // what
 9589          // a
 9590          // ˇlong
 9591          // method
 9592          // I
 9593          // sure
 9594          // hope
 9595          // it
 9596          // works
 9597    }"# });
 9598
 9599    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9600    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9601    cx.update(|_, cx| {
 9602        multi_buffer.update(cx, |multi_buffer, cx| {
 9603            multi_buffer.set_excerpts_for_path(
 9604                PathKey::for_buffer(&buffer, cx),
 9605                buffer,
 9606                [Point::new(1, 0)..Point::new(1, 0)],
 9607                3,
 9608                cx,
 9609            );
 9610        });
 9611    });
 9612
 9613    let editor2 = cx.new_window_entity(|window, cx| {
 9614        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9615    });
 9616
 9617    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9618    cx.update_editor(|editor, window, cx| {
 9619        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9620            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9621        })
 9622    });
 9623
 9624    cx.assert_editor_state(indoc! { "
 9625        fn a() {
 9626              // what
 9627              // a
 9628        ˇ      // long
 9629              // method"});
 9630
 9631    cx.update_editor(|editor, window, cx| {
 9632        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9633    });
 9634
 9635    // Although we could potentially make the action work when the syntax node
 9636    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9637    // did. Maybe we could also expand the excerpt to contain the range?
 9638    cx.assert_editor_state(indoc! { "
 9639        fn a() {
 9640              // what
 9641              // a
 9642        ˇ      // long
 9643              // method"});
 9644}
 9645
 9646#[gpui::test]
 9647async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9648    init_test(cx, |_| {});
 9649
 9650    let base_text = r#"
 9651        impl A {
 9652            // this is an uncommitted comment
 9653
 9654            fn b() {
 9655                c();
 9656            }
 9657
 9658            // this is another uncommitted comment
 9659
 9660            fn d() {
 9661                // e
 9662                // f
 9663            }
 9664        }
 9665
 9666        fn g() {
 9667            // h
 9668        }
 9669    "#
 9670    .unindent();
 9671
 9672    let text = r#"
 9673        ˇimpl A {
 9674
 9675            fn b() {
 9676                c();
 9677            }
 9678
 9679            fn d() {
 9680                // e
 9681                // f
 9682            }
 9683        }
 9684
 9685        fn g() {
 9686            // h
 9687        }
 9688    "#
 9689    .unindent();
 9690
 9691    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9692    cx.set_state(&text);
 9693    cx.set_head_text(&base_text);
 9694    cx.update_editor(|editor, window, cx| {
 9695        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9696    });
 9697
 9698    cx.assert_state_with_diff(
 9699        "
 9700        ˇimpl A {
 9701      -     // this is an uncommitted comment
 9702
 9703            fn b() {
 9704                c();
 9705            }
 9706
 9707      -     // this is another uncommitted comment
 9708      -
 9709            fn d() {
 9710                // e
 9711                // f
 9712            }
 9713        }
 9714
 9715        fn g() {
 9716            // h
 9717        }
 9718    "
 9719        .unindent(),
 9720    );
 9721
 9722    let expected_display_text = "
 9723        impl A {
 9724            // this is an uncommitted comment
 9725
 9726            fn b() {
 9727 9728            }
 9729
 9730            // this is another uncommitted comment
 9731
 9732            fn d() {
 9733 9734            }
 9735        }
 9736
 9737        fn g() {
 9738 9739        }
 9740        "
 9741    .unindent();
 9742
 9743    cx.update_editor(|editor, window, cx| {
 9744        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9745        assert_eq!(editor.display_text(cx), expected_display_text);
 9746    });
 9747}
 9748
 9749#[gpui::test]
 9750async fn test_autoindent(cx: &mut TestAppContext) {
 9751    init_test(cx, |_| {});
 9752
 9753    let language = Arc::new(
 9754        Language::new(
 9755            LanguageConfig {
 9756                brackets: BracketPairConfig {
 9757                    pairs: vec![
 9758                        BracketPair {
 9759                            start: "{".to_string(),
 9760                            end: "}".to_string(),
 9761                            close: false,
 9762                            surround: false,
 9763                            newline: true,
 9764                        },
 9765                        BracketPair {
 9766                            start: "(".to_string(),
 9767                            end: ")".to_string(),
 9768                            close: false,
 9769                            surround: false,
 9770                            newline: true,
 9771                        },
 9772                    ],
 9773                    ..Default::default()
 9774                },
 9775                ..Default::default()
 9776            },
 9777            Some(tree_sitter_rust::LANGUAGE.into()),
 9778        )
 9779        .with_indents_query(
 9780            r#"
 9781                (_ "(" ")" @end) @indent
 9782                (_ "{" "}" @end) @indent
 9783            "#,
 9784        )
 9785        .unwrap(),
 9786    );
 9787
 9788    let text = "fn a() {}";
 9789
 9790    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9791    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9792    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9793    editor
 9794        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9795        .await;
 9796
 9797    editor.update_in(cx, |editor, window, cx| {
 9798        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9799            s.select_ranges([
 9800                MultiBufferOffset(5)..MultiBufferOffset(5),
 9801                MultiBufferOffset(8)..MultiBufferOffset(8),
 9802                MultiBufferOffset(9)..MultiBufferOffset(9),
 9803            ])
 9804        });
 9805        editor.newline(&Newline, window, cx);
 9806        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9807        assert_eq!(
 9808            editor.selections.ranges(&editor.display_snapshot(cx)),
 9809            &[
 9810                Point::new(1, 4)..Point::new(1, 4),
 9811                Point::new(3, 4)..Point::new(3, 4),
 9812                Point::new(5, 0)..Point::new(5, 0)
 9813            ]
 9814        );
 9815    });
 9816}
 9817
 9818#[gpui::test]
 9819async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9820    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9821
 9822    let language = Arc::new(
 9823        Language::new(
 9824            LanguageConfig {
 9825                brackets: BracketPairConfig {
 9826                    pairs: vec![
 9827                        BracketPair {
 9828                            start: "{".to_string(),
 9829                            end: "}".to_string(),
 9830                            close: false,
 9831                            surround: false,
 9832                            newline: true,
 9833                        },
 9834                        BracketPair {
 9835                            start: "(".to_string(),
 9836                            end: ")".to_string(),
 9837                            close: false,
 9838                            surround: false,
 9839                            newline: true,
 9840                        },
 9841                    ],
 9842                    ..Default::default()
 9843                },
 9844                ..Default::default()
 9845            },
 9846            Some(tree_sitter_rust::LANGUAGE.into()),
 9847        )
 9848        .with_indents_query(
 9849            r#"
 9850                (_ "(" ")" @end) @indent
 9851                (_ "{" "}" @end) @indent
 9852            "#,
 9853        )
 9854        .unwrap(),
 9855    );
 9856
 9857    let text = "fn a() {}";
 9858
 9859    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9860    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9861    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9862    editor
 9863        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9864        .await;
 9865
 9866    editor.update_in(cx, |editor, window, cx| {
 9867        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9868            s.select_ranges([
 9869                MultiBufferOffset(5)..MultiBufferOffset(5),
 9870                MultiBufferOffset(8)..MultiBufferOffset(8),
 9871                MultiBufferOffset(9)..MultiBufferOffset(9),
 9872            ])
 9873        });
 9874        editor.newline(&Newline, window, cx);
 9875        assert_eq!(
 9876            editor.text(cx),
 9877            indoc!(
 9878                "
 9879                fn a(
 9880
 9881                ) {
 9882
 9883                }
 9884                "
 9885            )
 9886        );
 9887        assert_eq!(
 9888            editor.selections.ranges(&editor.display_snapshot(cx)),
 9889            &[
 9890                Point::new(1, 0)..Point::new(1, 0),
 9891                Point::new(3, 0)..Point::new(3, 0),
 9892                Point::new(5, 0)..Point::new(5, 0)
 9893            ]
 9894        );
 9895    });
 9896}
 9897
 9898#[gpui::test]
 9899async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9900    init_test(cx, |settings| {
 9901        settings.defaults.auto_indent = Some(true);
 9902        settings.languages.0.insert(
 9903            "python".into(),
 9904            LanguageSettingsContent {
 9905                auto_indent: Some(false),
 9906                ..Default::default()
 9907            },
 9908        );
 9909    });
 9910
 9911    let mut cx = EditorTestContext::new(cx).await;
 9912
 9913    let injected_language = Arc::new(
 9914        Language::new(
 9915            LanguageConfig {
 9916                brackets: BracketPairConfig {
 9917                    pairs: vec![
 9918                        BracketPair {
 9919                            start: "{".to_string(),
 9920                            end: "}".to_string(),
 9921                            close: false,
 9922                            surround: false,
 9923                            newline: true,
 9924                        },
 9925                        BracketPair {
 9926                            start: "(".to_string(),
 9927                            end: ")".to_string(),
 9928                            close: true,
 9929                            surround: false,
 9930                            newline: true,
 9931                        },
 9932                    ],
 9933                    ..Default::default()
 9934                },
 9935                name: "python".into(),
 9936                ..Default::default()
 9937            },
 9938            Some(tree_sitter_python::LANGUAGE.into()),
 9939        )
 9940        .with_indents_query(
 9941            r#"
 9942                (_ "(" ")" @end) @indent
 9943                (_ "{" "}" @end) @indent
 9944            "#,
 9945        )
 9946        .unwrap(),
 9947    );
 9948
 9949    let language = Arc::new(
 9950        Language::new(
 9951            LanguageConfig {
 9952                brackets: BracketPairConfig {
 9953                    pairs: vec![
 9954                        BracketPair {
 9955                            start: "{".to_string(),
 9956                            end: "}".to_string(),
 9957                            close: false,
 9958                            surround: false,
 9959                            newline: true,
 9960                        },
 9961                        BracketPair {
 9962                            start: "(".to_string(),
 9963                            end: ")".to_string(),
 9964                            close: true,
 9965                            surround: false,
 9966                            newline: true,
 9967                        },
 9968                    ],
 9969                    ..Default::default()
 9970                },
 9971                name: LanguageName::new("rust"),
 9972                ..Default::default()
 9973            },
 9974            Some(tree_sitter_rust::LANGUAGE.into()),
 9975        )
 9976        .with_indents_query(
 9977            r#"
 9978                (_ "(" ")" @end) @indent
 9979                (_ "{" "}" @end) @indent
 9980            "#,
 9981        )
 9982        .unwrap()
 9983        .with_injection_query(
 9984            r#"
 9985            (macro_invocation
 9986                macro: (identifier) @_macro_name
 9987                (token_tree) @injection.content
 9988                (#set! injection.language "python"))
 9989           "#,
 9990        )
 9991        .unwrap(),
 9992    );
 9993
 9994    cx.language_registry().add(injected_language);
 9995    cx.language_registry().add(language.clone());
 9996
 9997    cx.update_buffer(|buffer, cx| {
 9998        buffer.set_language(Some(language), cx);
 9999    });
10000
10001    cx.set_state(r#"struct A {ˇ}"#);
10002
10003    cx.update_editor(|editor, window, cx| {
10004        editor.newline(&Default::default(), window, cx);
10005    });
10006
10007    cx.assert_editor_state(indoc!(
10008        "struct A {
10009            ˇ
10010        }"
10011    ));
10012
10013    cx.set_state(r#"select_biased!(ˇ)"#);
10014
10015    cx.update_editor(|editor, window, cx| {
10016        editor.newline(&Default::default(), window, cx);
10017        editor.handle_input("def ", window, cx);
10018        editor.handle_input("(", window, cx);
10019        editor.newline(&Default::default(), window, cx);
10020        editor.handle_input("a", window, cx);
10021    });
10022
10023    cx.assert_editor_state(indoc!(
10024        "select_biased!(
10025        def (
1002610027        )
10028        )"
10029    ));
10030}
10031
10032#[gpui::test]
10033async fn test_autoindent_selections(cx: &mut TestAppContext) {
10034    init_test(cx, |_| {});
10035
10036    {
10037        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10038        cx.set_state(indoc! {"
10039            impl A {
10040
10041                fn b() {}
10042
10043            «fn c() {
10044
10045            }ˇ»
10046            }
10047        "});
10048
10049        cx.update_editor(|editor, window, cx| {
10050            editor.autoindent(&Default::default(), window, cx);
10051        });
10052
10053        cx.assert_editor_state(indoc! {"
10054            impl A {
10055
10056                fn b() {}
10057
10058                «fn c() {
10059
10060                }ˇ»
10061            }
10062        "});
10063    }
10064
10065    {
10066        let mut cx = EditorTestContext::new_multibuffer(
10067            cx,
10068            [indoc! { "
10069                impl A {
10070                «
10071                // a
10072                fn b(){}
10073                »
10074                «
10075                    }
10076                    fn c(){}
10077                »
10078            "}],
10079        );
10080
10081        let buffer = cx.update_editor(|editor, _, cx| {
10082            let buffer = editor.buffer().update(cx, |buffer, _| {
10083                buffer.all_buffers().iter().next().unwrap().clone()
10084            });
10085            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10086            buffer
10087        });
10088
10089        cx.run_until_parked();
10090        cx.update_editor(|editor, window, cx| {
10091            editor.select_all(&Default::default(), window, cx);
10092            editor.autoindent(&Default::default(), window, cx)
10093        });
10094        cx.run_until_parked();
10095
10096        cx.update(|_, cx| {
10097            assert_eq!(
10098                buffer.read(cx).text(),
10099                indoc! { "
10100                    impl A {
10101
10102                        // a
10103                        fn b(){}
10104
10105
10106                    }
10107                    fn c(){}
10108
10109                " }
10110            )
10111        });
10112    }
10113}
10114
10115#[gpui::test]
10116async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10117    init_test(cx, |_| {});
10118
10119    let mut cx = EditorTestContext::new(cx).await;
10120
10121    let language = Arc::new(Language::new(
10122        LanguageConfig {
10123            brackets: BracketPairConfig {
10124                pairs: vec![
10125                    BracketPair {
10126                        start: "{".to_string(),
10127                        end: "}".to_string(),
10128                        close: true,
10129                        surround: true,
10130                        newline: true,
10131                    },
10132                    BracketPair {
10133                        start: "(".to_string(),
10134                        end: ")".to_string(),
10135                        close: true,
10136                        surround: true,
10137                        newline: true,
10138                    },
10139                    BracketPair {
10140                        start: "/*".to_string(),
10141                        end: " */".to_string(),
10142                        close: true,
10143                        surround: true,
10144                        newline: true,
10145                    },
10146                    BracketPair {
10147                        start: "[".to_string(),
10148                        end: "]".to_string(),
10149                        close: false,
10150                        surround: false,
10151                        newline: true,
10152                    },
10153                    BracketPair {
10154                        start: "\"".to_string(),
10155                        end: "\"".to_string(),
10156                        close: true,
10157                        surround: true,
10158                        newline: false,
10159                    },
10160                    BracketPair {
10161                        start: "<".to_string(),
10162                        end: ">".to_string(),
10163                        close: false,
10164                        surround: true,
10165                        newline: true,
10166                    },
10167                ],
10168                ..Default::default()
10169            },
10170            autoclose_before: "})]".to_string(),
10171            ..Default::default()
10172        },
10173        Some(tree_sitter_rust::LANGUAGE.into()),
10174    ));
10175
10176    cx.language_registry().add(language.clone());
10177    cx.update_buffer(|buffer, cx| {
10178        buffer.set_language(Some(language), cx);
10179    });
10180
10181    cx.set_state(
10182        &r#"
10183            🏀ˇ
10184            εˇ
10185            ❤️ˇ
10186        "#
10187        .unindent(),
10188    );
10189
10190    // autoclose multiple nested brackets at multiple cursors
10191    cx.update_editor(|editor, window, cx| {
10192        editor.handle_input("{", window, cx);
10193        editor.handle_input("{", window, cx);
10194        editor.handle_input("{", window, cx);
10195    });
10196    cx.assert_editor_state(
10197        &"
10198            🏀{{{ˇ}}}
10199            ε{{{ˇ}}}
10200            ❤️{{{ˇ}}}
10201        "
10202        .unindent(),
10203    );
10204
10205    // insert a different closing bracket
10206    cx.update_editor(|editor, window, cx| {
10207        editor.handle_input(")", window, cx);
10208    });
10209    cx.assert_editor_state(
10210        &"
10211            🏀{{{)ˇ}}}
10212            ε{{{)ˇ}}}
10213            ❤️{{{)ˇ}}}
10214        "
10215        .unindent(),
10216    );
10217
10218    // skip over the auto-closed brackets when typing a closing bracket
10219    cx.update_editor(|editor, window, cx| {
10220        editor.move_right(&MoveRight, window, cx);
10221        editor.handle_input("}", window, cx);
10222        editor.handle_input("}", window, cx);
10223        editor.handle_input("}", window, cx);
10224    });
10225    cx.assert_editor_state(
10226        &"
10227            🏀{{{)}}}}ˇ
10228            ε{{{)}}}}ˇ
10229            ❤️{{{)}}}}ˇ
10230        "
10231        .unindent(),
10232    );
10233
10234    // autoclose multi-character pairs
10235    cx.set_state(
10236        &"
10237            ˇ
10238            ˇ
10239        "
10240        .unindent(),
10241    );
10242    cx.update_editor(|editor, window, cx| {
10243        editor.handle_input("/", window, cx);
10244        editor.handle_input("*", window, cx);
10245    });
10246    cx.assert_editor_state(
10247        &"
10248            /*ˇ */
10249            /*ˇ */
10250        "
10251        .unindent(),
10252    );
10253
10254    // one cursor autocloses a multi-character pair, one cursor
10255    // does not autoclose.
10256    cx.set_state(
10257        &"
1025810259            ˇ
10260        "
10261        .unindent(),
10262    );
10263    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10264    cx.assert_editor_state(
10265        &"
10266            /*ˇ */
1026710268        "
10269        .unindent(),
10270    );
10271
10272    // Don't autoclose if the next character isn't whitespace and isn't
10273    // listed in the language's "autoclose_before" section.
10274    cx.set_state("ˇa b");
10275    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10276    cx.assert_editor_state("{ˇa b");
10277
10278    // Don't autoclose if `close` is false for the bracket pair
10279    cx.set_state("ˇ");
10280    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10281    cx.assert_editor_state("");
10282
10283    // Surround with brackets if text is selected
10284    cx.set_state("«aˇ» b");
10285    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10286    cx.assert_editor_state("{«aˇ»} b");
10287
10288    // Autoclose when not immediately after a word character
10289    cx.set_state("a ˇ");
10290    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10291    cx.assert_editor_state("a \"ˇ\"");
10292
10293    // Autoclose pair where the start and end characters are the same
10294    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10295    cx.assert_editor_state("a \"\"ˇ");
10296
10297    // Don't autoclose when immediately after a word character
10298    cx.set_state("");
10299    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10300    cx.assert_editor_state("a\"ˇ");
10301
10302    // Do autoclose when after a non-word character
10303    cx.set_state("");
10304    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10305    cx.assert_editor_state("{\"ˇ\"");
10306
10307    // Non identical pairs autoclose regardless of preceding character
10308    cx.set_state("");
10309    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10310    cx.assert_editor_state("a{ˇ}");
10311
10312    // Don't autoclose pair if autoclose is disabled
10313    cx.set_state("ˇ");
10314    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10315    cx.assert_editor_state("");
10316
10317    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10318    cx.set_state("«aˇ» b");
10319    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10320    cx.assert_editor_state("<«aˇ»> b");
10321}
10322
10323#[gpui::test]
10324async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10325    init_test(cx, |settings| {
10326        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10327    });
10328
10329    let mut cx = EditorTestContext::new(cx).await;
10330
10331    let language = Arc::new(Language::new(
10332        LanguageConfig {
10333            brackets: BracketPairConfig {
10334                pairs: vec![
10335                    BracketPair {
10336                        start: "{".to_string(),
10337                        end: "}".to_string(),
10338                        close: true,
10339                        surround: true,
10340                        newline: true,
10341                    },
10342                    BracketPair {
10343                        start: "(".to_string(),
10344                        end: ")".to_string(),
10345                        close: true,
10346                        surround: true,
10347                        newline: true,
10348                    },
10349                    BracketPair {
10350                        start: "[".to_string(),
10351                        end: "]".to_string(),
10352                        close: false,
10353                        surround: false,
10354                        newline: true,
10355                    },
10356                ],
10357                ..Default::default()
10358            },
10359            autoclose_before: "})]".to_string(),
10360            ..Default::default()
10361        },
10362        Some(tree_sitter_rust::LANGUAGE.into()),
10363    ));
10364
10365    cx.language_registry().add(language.clone());
10366    cx.update_buffer(|buffer, cx| {
10367        buffer.set_language(Some(language), cx);
10368    });
10369
10370    cx.set_state(
10371        &"
10372            ˇ
10373            ˇ
10374            ˇ
10375        "
10376        .unindent(),
10377    );
10378
10379    // ensure only matching closing brackets are skipped over
10380    cx.update_editor(|editor, window, cx| {
10381        editor.handle_input("}", window, cx);
10382        editor.move_left(&MoveLeft, window, cx);
10383        editor.handle_input(")", window, cx);
10384        editor.move_left(&MoveLeft, window, cx);
10385    });
10386    cx.assert_editor_state(
10387        &"
10388            ˇ)}
10389            ˇ)}
10390            ˇ)}
10391        "
10392        .unindent(),
10393    );
10394
10395    // skip-over closing brackets at multiple cursors
10396    cx.update_editor(|editor, window, cx| {
10397        editor.handle_input(")", window, cx);
10398        editor.handle_input("}", window, cx);
10399    });
10400    cx.assert_editor_state(
10401        &"
10402            )}ˇ
10403            )}ˇ
10404            )}ˇ
10405        "
10406        .unindent(),
10407    );
10408
10409    // ignore non-close brackets
10410    cx.update_editor(|editor, window, cx| {
10411        editor.handle_input("]", window, cx);
10412        editor.move_left(&MoveLeft, window, cx);
10413        editor.handle_input("]", window, cx);
10414    });
10415    cx.assert_editor_state(
10416        &"
10417            )}]ˇ]
10418            )}]ˇ]
10419            )}]ˇ]
10420        "
10421        .unindent(),
10422    );
10423}
10424
10425#[gpui::test]
10426async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10427    init_test(cx, |_| {});
10428
10429    let mut cx = EditorTestContext::new(cx).await;
10430
10431    let html_language = Arc::new(
10432        Language::new(
10433            LanguageConfig {
10434                name: "HTML".into(),
10435                brackets: BracketPairConfig {
10436                    pairs: vec![
10437                        BracketPair {
10438                            start: "<".into(),
10439                            end: ">".into(),
10440                            close: true,
10441                            ..Default::default()
10442                        },
10443                        BracketPair {
10444                            start: "{".into(),
10445                            end: "}".into(),
10446                            close: true,
10447                            ..Default::default()
10448                        },
10449                        BracketPair {
10450                            start: "(".into(),
10451                            end: ")".into(),
10452                            close: true,
10453                            ..Default::default()
10454                        },
10455                    ],
10456                    ..Default::default()
10457                },
10458                autoclose_before: "})]>".into(),
10459                ..Default::default()
10460            },
10461            Some(tree_sitter_html::LANGUAGE.into()),
10462        )
10463        .with_injection_query(
10464            r#"
10465            (script_element
10466                (raw_text) @injection.content
10467                (#set! injection.language "javascript"))
10468            "#,
10469        )
10470        .unwrap(),
10471    );
10472
10473    let javascript_language = Arc::new(Language::new(
10474        LanguageConfig {
10475            name: "JavaScript".into(),
10476            brackets: BracketPairConfig {
10477                pairs: vec![
10478                    BracketPair {
10479                        start: "/*".into(),
10480                        end: " */".into(),
10481                        close: true,
10482                        ..Default::default()
10483                    },
10484                    BracketPair {
10485                        start: "{".into(),
10486                        end: "}".into(),
10487                        close: true,
10488                        ..Default::default()
10489                    },
10490                    BracketPair {
10491                        start: "(".into(),
10492                        end: ")".into(),
10493                        close: true,
10494                        ..Default::default()
10495                    },
10496                ],
10497                ..Default::default()
10498            },
10499            autoclose_before: "})]>".into(),
10500            ..Default::default()
10501        },
10502        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10503    ));
10504
10505    cx.language_registry().add(html_language.clone());
10506    cx.language_registry().add(javascript_language);
10507    cx.executor().run_until_parked();
10508
10509    cx.update_buffer(|buffer, cx| {
10510        buffer.set_language(Some(html_language), cx);
10511    });
10512
10513    cx.set_state(
10514        &r#"
10515            <body>ˇ
10516                <script>
10517                    var x = 1;ˇ
10518                </script>
10519            </body>ˇ
10520        "#
10521        .unindent(),
10522    );
10523
10524    // Precondition: different languages are active at different locations.
10525    cx.update_editor(|editor, window, cx| {
10526        let snapshot = editor.snapshot(window, cx);
10527        let cursors = editor
10528            .selections
10529            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10530        let languages = cursors
10531            .iter()
10532            .map(|c| snapshot.language_at(c.start).unwrap().name())
10533            .collect::<Vec<_>>();
10534        assert_eq!(
10535            languages,
10536            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10537        );
10538    });
10539
10540    // Angle brackets autoclose in HTML, but not JavaScript.
10541    cx.update_editor(|editor, window, cx| {
10542        editor.handle_input("<", window, cx);
10543        editor.handle_input("a", window, cx);
10544    });
10545    cx.assert_editor_state(
10546        &r#"
10547            <body><aˇ>
10548                <script>
10549                    var x = 1;<aˇ
10550                </script>
10551            </body><aˇ>
10552        "#
10553        .unindent(),
10554    );
10555
10556    // Curly braces and parens autoclose in both HTML and JavaScript.
10557    cx.update_editor(|editor, window, cx| {
10558        editor.handle_input(" b=", window, cx);
10559        editor.handle_input("{", window, cx);
10560        editor.handle_input("c", window, cx);
10561        editor.handle_input("(", window, cx);
10562    });
10563    cx.assert_editor_state(
10564        &r#"
10565            <body><a b={c(ˇ)}>
10566                <script>
10567                    var x = 1;<a b={c(ˇ)}
10568                </script>
10569            </body><a b={c(ˇ)}>
10570        "#
10571        .unindent(),
10572    );
10573
10574    // Brackets that were already autoclosed are skipped.
10575    cx.update_editor(|editor, window, cx| {
10576        editor.handle_input(")", window, cx);
10577        editor.handle_input("d", window, cx);
10578        editor.handle_input("}", window, cx);
10579    });
10580    cx.assert_editor_state(
10581        &r#"
10582            <body><a b={c()d}ˇ>
10583                <script>
10584                    var x = 1;<a b={c()d}ˇ
10585                </script>
10586            </body><a b={c()d}ˇ>
10587        "#
10588        .unindent(),
10589    );
10590    cx.update_editor(|editor, window, cx| {
10591        editor.handle_input(">", window, cx);
10592    });
10593    cx.assert_editor_state(
10594        &r#"
10595            <body><a b={c()d}>ˇ
10596                <script>
10597                    var x = 1;<a b={c()d}>ˇ
10598                </script>
10599            </body><a b={c()d}>ˇ
10600        "#
10601        .unindent(),
10602    );
10603
10604    // Reset
10605    cx.set_state(
10606        &r#"
10607            <body>ˇ
10608                <script>
10609                    var x = 1;ˇ
10610                </script>
10611            </body>ˇ
10612        "#
10613        .unindent(),
10614    );
10615
10616    cx.update_editor(|editor, window, cx| {
10617        editor.handle_input("<", window, cx);
10618    });
10619    cx.assert_editor_state(
10620        &r#"
10621            <body><ˇ>
10622                <script>
10623                    var x = 1;<ˇ
10624                </script>
10625            </body><ˇ>
10626        "#
10627        .unindent(),
10628    );
10629
10630    // When backspacing, the closing angle brackets are removed.
10631    cx.update_editor(|editor, window, cx| {
10632        editor.backspace(&Backspace, window, cx);
10633    });
10634    cx.assert_editor_state(
10635        &r#"
10636            <body>ˇ
10637                <script>
10638                    var x = 1;ˇ
10639                </script>
10640            </body>ˇ
10641        "#
10642        .unindent(),
10643    );
10644
10645    // Block comments autoclose in JavaScript, but not HTML.
10646    cx.update_editor(|editor, window, cx| {
10647        editor.handle_input("/", window, cx);
10648        editor.handle_input("*", window, cx);
10649    });
10650    cx.assert_editor_state(
10651        &r#"
10652            <body>/*ˇ
10653                <script>
10654                    var x = 1;/*ˇ */
10655                </script>
10656            </body>/*ˇ
10657        "#
10658        .unindent(),
10659    );
10660}
10661
10662#[gpui::test]
10663async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10664    init_test(cx, |_| {});
10665
10666    let mut cx = EditorTestContext::new(cx).await;
10667
10668    let rust_language = Arc::new(
10669        Language::new(
10670            LanguageConfig {
10671                name: "Rust".into(),
10672                brackets: serde_json::from_value(json!([
10673                    { "start": "{", "end": "}", "close": true, "newline": true },
10674                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10675                ]))
10676                .unwrap(),
10677                autoclose_before: "})]>".into(),
10678                ..Default::default()
10679            },
10680            Some(tree_sitter_rust::LANGUAGE.into()),
10681        )
10682        .with_override_query("(string_literal) @string")
10683        .unwrap(),
10684    );
10685
10686    cx.language_registry().add(rust_language.clone());
10687    cx.update_buffer(|buffer, cx| {
10688        buffer.set_language(Some(rust_language), cx);
10689    });
10690
10691    cx.set_state(
10692        &r#"
10693            let x = ˇ
10694        "#
10695        .unindent(),
10696    );
10697
10698    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10699    cx.update_editor(|editor, window, cx| {
10700        editor.handle_input("\"", window, cx);
10701    });
10702    cx.assert_editor_state(
10703        &r#"
10704            let x = "ˇ"
10705        "#
10706        .unindent(),
10707    );
10708
10709    // Inserting another quotation mark. The cursor moves across the existing
10710    // automatically-inserted quotation mark.
10711    cx.update_editor(|editor, window, cx| {
10712        editor.handle_input("\"", window, cx);
10713    });
10714    cx.assert_editor_state(
10715        &r#"
10716            let x = ""ˇ
10717        "#
10718        .unindent(),
10719    );
10720
10721    // Reset
10722    cx.set_state(
10723        &r#"
10724            let x = ˇ
10725        "#
10726        .unindent(),
10727    );
10728
10729    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10730    cx.update_editor(|editor, window, cx| {
10731        editor.handle_input("\"", window, cx);
10732        editor.handle_input(" ", window, cx);
10733        editor.move_left(&Default::default(), window, cx);
10734        editor.handle_input("\\", window, cx);
10735        editor.handle_input("\"", window, cx);
10736    });
10737    cx.assert_editor_state(
10738        &r#"
10739            let x = "\"ˇ "
10740        "#
10741        .unindent(),
10742    );
10743
10744    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10745    // mark. Nothing is inserted.
10746    cx.update_editor(|editor, window, cx| {
10747        editor.move_right(&Default::default(), window, cx);
10748        editor.handle_input("\"", window, cx);
10749    });
10750    cx.assert_editor_state(
10751        &r#"
10752            let x = "\" "ˇ
10753        "#
10754        .unindent(),
10755    );
10756}
10757
10758#[gpui::test]
10759async fn test_surround_with_pair(cx: &mut TestAppContext) {
10760    init_test(cx, |_| {});
10761
10762    let language = Arc::new(Language::new(
10763        LanguageConfig {
10764            brackets: BracketPairConfig {
10765                pairs: vec![
10766                    BracketPair {
10767                        start: "{".to_string(),
10768                        end: "}".to_string(),
10769                        close: true,
10770                        surround: true,
10771                        newline: true,
10772                    },
10773                    BracketPair {
10774                        start: "/* ".to_string(),
10775                        end: "*/".to_string(),
10776                        close: true,
10777                        surround: true,
10778                        ..Default::default()
10779                    },
10780                ],
10781                ..Default::default()
10782            },
10783            ..Default::default()
10784        },
10785        Some(tree_sitter_rust::LANGUAGE.into()),
10786    ));
10787
10788    let text = r#"
10789        a
10790        b
10791        c
10792    "#
10793    .unindent();
10794
10795    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10796    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10797    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10798    editor
10799        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10800        .await;
10801
10802    editor.update_in(cx, |editor, window, cx| {
10803        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10804            s.select_display_ranges([
10805                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10806                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10807                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10808            ])
10809        });
10810
10811        editor.handle_input("{", window, cx);
10812        editor.handle_input("{", window, cx);
10813        editor.handle_input("{", window, cx);
10814        assert_eq!(
10815            editor.text(cx),
10816            "
10817                {{{a}}}
10818                {{{b}}}
10819                {{{c}}}
10820            "
10821            .unindent()
10822        );
10823        assert_eq!(
10824            display_ranges(editor, cx),
10825            [
10826                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10827                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10828                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10829            ]
10830        );
10831
10832        editor.undo(&Undo, window, cx);
10833        editor.undo(&Undo, window, cx);
10834        editor.undo(&Undo, window, cx);
10835        assert_eq!(
10836            editor.text(cx),
10837            "
10838                a
10839                b
10840                c
10841            "
10842            .unindent()
10843        );
10844        assert_eq!(
10845            display_ranges(editor, cx),
10846            [
10847                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10848                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10849                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10850            ]
10851        );
10852
10853        // Ensure inserting the first character of a multi-byte bracket pair
10854        // doesn't surround the selections with the bracket.
10855        editor.handle_input("/", window, cx);
10856        assert_eq!(
10857            editor.text(cx),
10858            "
10859                /
10860                /
10861                /
10862            "
10863            .unindent()
10864        );
10865        assert_eq!(
10866            display_ranges(editor, cx),
10867            [
10868                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10869                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10870                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10871            ]
10872        );
10873
10874        editor.undo(&Undo, window, cx);
10875        assert_eq!(
10876            editor.text(cx),
10877            "
10878                a
10879                b
10880                c
10881            "
10882            .unindent()
10883        );
10884        assert_eq!(
10885            display_ranges(editor, cx),
10886            [
10887                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10888                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10889                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10890            ]
10891        );
10892
10893        // Ensure inserting the last character of a multi-byte bracket pair
10894        // doesn't surround the selections with the bracket.
10895        editor.handle_input("*", window, cx);
10896        assert_eq!(
10897            editor.text(cx),
10898            "
10899                *
10900                *
10901                *
10902            "
10903            .unindent()
10904        );
10905        assert_eq!(
10906            display_ranges(editor, cx),
10907            [
10908                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10909                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10910                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10911            ]
10912        );
10913    });
10914}
10915
10916#[gpui::test]
10917async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10918    init_test(cx, |_| {});
10919
10920    let language = Arc::new(Language::new(
10921        LanguageConfig {
10922            brackets: BracketPairConfig {
10923                pairs: vec![BracketPair {
10924                    start: "{".to_string(),
10925                    end: "}".to_string(),
10926                    close: true,
10927                    surround: true,
10928                    newline: true,
10929                }],
10930                ..Default::default()
10931            },
10932            autoclose_before: "}".to_string(),
10933            ..Default::default()
10934        },
10935        Some(tree_sitter_rust::LANGUAGE.into()),
10936    ));
10937
10938    let text = r#"
10939        a
10940        b
10941        c
10942    "#
10943    .unindent();
10944
10945    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10946    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10947    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10948    editor
10949        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10950        .await;
10951
10952    editor.update_in(cx, |editor, window, cx| {
10953        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10954            s.select_ranges([
10955                Point::new(0, 1)..Point::new(0, 1),
10956                Point::new(1, 1)..Point::new(1, 1),
10957                Point::new(2, 1)..Point::new(2, 1),
10958            ])
10959        });
10960
10961        editor.handle_input("{", window, cx);
10962        editor.handle_input("{", window, cx);
10963        editor.handle_input("_", window, cx);
10964        assert_eq!(
10965            editor.text(cx),
10966            "
10967                a{{_}}
10968                b{{_}}
10969                c{{_}}
10970            "
10971            .unindent()
10972        );
10973        assert_eq!(
10974            editor
10975                .selections
10976                .ranges::<Point>(&editor.display_snapshot(cx)),
10977            [
10978                Point::new(0, 4)..Point::new(0, 4),
10979                Point::new(1, 4)..Point::new(1, 4),
10980                Point::new(2, 4)..Point::new(2, 4)
10981            ]
10982        );
10983
10984        editor.backspace(&Default::default(), window, cx);
10985        editor.backspace(&Default::default(), window, cx);
10986        assert_eq!(
10987            editor.text(cx),
10988            "
10989                a{}
10990                b{}
10991                c{}
10992            "
10993            .unindent()
10994        );
10995        assert_eq!(
10996            editor
10997                .selections
10998                .ranges::<Point>(&editor.display_snapshot(cx)),
10999            [
11000                Point::new(0, 2)..Point::new(0, 2),
11001                Point::new(1, 2)..Point::new(1, 2),
11002                Point::new(2, 2)..Point::new(2, 2)
11003            ]
11004        );
11005
11006        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11007        assert_eq!(
11008            editor.text(cx),
11009            "
11010                a
11011                b
11012                c
11013            "
11014            .unindent()
11015        );
11016        assert_eq!(
11017            editor
11018                .selections
11019                .ranges::<Point>(&editor.display_snapshot(cx)),
11020            [
11021                Point::new(0, 1)..Point::new(0, 1),
11022                Point::new(1, 1)..Point::new(1, 1),
11023                Point::new(2, 1)..Point::new(2, 1)
11024            ]
11025        );
11026    });
11027}
11028
11029#[gpui::test]
11030async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11031    init_test(cx, |settings| {
11032        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11033    });
11034
11035    let mut cx = EditorTestContext::new(cx).await;
11036
11037    let language = Arc::new(Language::new(
11038        LanguageConfig {
11039            brackets: BracketPairConfig {
11040                pairs: vec![
11041                    BracketPair {
11042                        start: "{".to_string(),
11043                        end: "}".to_string(),
11044                        close: true,
11045                        surround: true,
11046                        newline: true,
11047                    },
11048                    BracketPair {
11049                        start: "(".to_string(),
11050                        end: ")".to_string(),
11051                        close: true,
11052                        surround: true,
11053                        newline: true,
11054                    },
11055                    BracketPair {
11056                        start: "[".to_string(),
11057                        end: "]".to_string(),
11058                        close: false,
11059                        surround: true,
11060                        newline: true,
11061                    },
11062                ],
11063                ..Default::default()
11064            },
11065            autoclose_before: "})]".to_string(),
11066            ..Default::default()
11067        },
11068        Some(tree_sitter_rust::LANGUAGE.into()),
11069    ));
11070
11071    cx.language_registry().add(language.clone());
11072    cx.update_buffer(|buffer, cx| {
11073        buffer.set_language(Some(language), cx);
11074    });
11075
11076    cx.set_state(
11077        &"
11078            {(ˇ)}
11079            [[ˇ]]
11080            {(ˇ)}
11081        "
11082        .unindent(),
11083    );
11084
11085    cx.update_editor(|editor, window, cx| {
11086        editor.backspace(&Default::default(), window, cx);
11087        editor.backspace(&Default::default(), window, cx);
11088    });
11089
11090    cx.assert_editor_state(
11091        &"
11092            ˇ
11093            ˇ]]
11094            ˇ
11095        "
11096        .unindent(),
11097    );
11098
11099    cx.update_editor(|editor, window, cx| {
11100        editor.handle_input("{", window, cx);
11101        editor.handle_input("{", window, cx);
11102        editor.move_right(&MoveRight, window, cx);
11103        editor.move_right(&MoveRight, window, cx);
11104        editor.move_left(&MoveLeft, window, cx);
11105        editor.move_left(&MoveLeft, window, cx);
11106        editor.backspace(&Default::default(), window, cx);
11107    });
11108
11109    cx.assert_editor_state(
11110        &"
11111            {ˇ}
11112            {ˇ}]]
11113            {ˇ}
11114        "
11115        .unindent(),
11116    );
11117
11118    cx.update_editor(|editor, window, cx| {
11119        editor.backspace(&Default::default(), window, cx);
11120    });
11121
11122    cx.assert_editor_state(
11123        &"
11124            ˇ
11125            ˇ]]
11126            ˇ
11127        "
11128        .unindent(),
11129    );
11130}
11131
11132#[gpui::test]
11133async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11134    init_test(cx, |_| {});
11135
11136    let language = Arc::new(Language::new(
11137        LanguageConfig::default(),
11138        Some(tree_sitter_rust::LANGUAGE.into()),
11139    ));
11140
11141    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11142    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11143    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11144    editor
11145        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11146        .await;
11147
11148    editor.update_in(cx, |editor, window, cx| {
11149        editor.set_auto_replace_emoji_shortcode(true);
11150
11151        editor.handle_input("Hello ", window, cx);
11152        editor.handle_input(":wave", window, cx);
11153        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11154
11155        editor.handle_input(":", window, cx);
11156        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11157
11158        editor.handle_input(" :smile", window, cx);
11159        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11160
11161        editor.handle_input(":", window, cx);
11162        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11163
11164        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11165        editor.handle_input(":wave", window, cx);
11166        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11167
11168        editor.handle_input(":", window, cx);
11169        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11170
11171        editor.handle_input(":1", window, cx);
11172        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11173
11174        editor.handle_input(":", window, cx);
11175        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11176
11177        // Ensure shortcode does not get replaced when it is part of a word
11178        editor.handle_input(" Test:wave", window, cx);
11179        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11180
11181        editor.handle_input(":", window, cx);
11182        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11183
11184        editor.set_auto_replace_emoji_shortcode(false);
11185
11186        // Ensure shortcode does not get replaced when auto replace is off
11187        editor.handle_input(" :wave", window, cx);
11188        assert_eq!(
11189            editor.text(cx),
11190            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11191        );
11192
11193        editor.handle_input(":", window, cx);
11194        assert_eq!(
11195            editor.text(cx),
11196            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11197        );
11198    });
11199}
11200
11201#[gpui::test]
11202async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11203    init_test(cx, |_| {});
11204
11205    let (text, insertion_ranges) = marked_text_ranges(
11206        indoc! {"
11207            ˇ
11208        "},
11209        false,
11210    );
11211
11212    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11213    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11214
11215    _ = editor.update_in(cx, |editor, window, cx| {
11216        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11217
11218        editor
11219            .insert_snippet(
11220                &insertion_ranges
11221                    .iter()
11222                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11223                    .collect::<Vec<_>>(),
11224                snippet,
11225                window,
11226                cx,
11227            )
11228            .unwrap();
11229
11230        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11231            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11232            assert_eq!(editor.text(cx), expected_text);
11233            assert_eq!(
11234                editor.selections.ranges(&editor.display_snapshot(cx)),
11235                selection_ranges
11236                    .iter()
11237                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11238                    .collect::<Vec<_>>()
11239            );
11240        }
11241
11242        assert(
11243            editor,
11244            cx,
11245            indoc! {"
11246            type «» =•
11247            "},
11248        );
11249
11250        assert!(editor.context_menu_visible(), "There should be a matches");
11251    });
11252}
11253
11254#[gpui::test]
11255async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11256    init_test(cx, |_| {});
11257
11258    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11259        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11260        assert_eq!(editor.text(cx), expected_text);
11261        assert_eq!(
11262            editor.selections.ranges(&editor.display_snapshot(cx)),
11263            selection_ranges
11264                .iter()
11265                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11266                .collect::<Vec<_>>()
11267        );
11268    }
11269
11270    let (text, insertion_ranges) = marked_text_ranges(
11271        indoc! {"
11272            ˇ
11273        "},
11274        false,
11275    );
11276
11277    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11278    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11279
11280    _ = editor.update_in(cx, |editor, window, cx| {
11281        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11282
11283        editor
11284            .insert_snippet(
11285                &insertion_ranges
11286                    .iter()
11287                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11288                    .collect::<Vec<_>>(),
11289                snippet,
11290                window,
11291                cx,
11292            )
11293            .unwrap();
11294
11295        assert_state(
11296            editor,
11297            cx,
11298            indoc! {"
11299            type «» = ;•
11300            "},
11301        );
11302
11303        assert!(
11304            editor.context_menu_visible(),
11305            "Context menu should be visible for placeholder choices"
11306        );
11307
11308        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11309
11310        assert_state(
11311            editor,
11312            cx,
11313            indoc! {"
11314            type  = «»;•
11315            "},
11316        );
11317
11318        assert!(
11319            !editor.context_menu_visible(),
11320            "Context menu should be hidden after moving to next tabstop"
11321        );
11322
11323        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11324
11325        assert_state(
11326            editor,
11327            cx,
11328            indoc! {"
11329            type  = ; ˇ
11330            "},
11331        );
11332
11333        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11334
11335        assert_state(
11336            editor,
11337            cx,
11338            indoc! {"
11339            type  = ; ˇ
11340            "},
11341        );
11342    });
11343
11344    _ = editor.update_in(cx, |editor, window, cx| {
11345        editor.select_all(&SelectAll, window, cx);
11346        editor.backspace(&Backspace, window, cx);
11347
11348        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11349        let insertion_ranges = editor
11350            .selections
11351            .all(&editor.display_snapshot(cx))
11352            .iter()
11353            .map(|s| s.range())
11354            .collect::<Vec<_>>();
11355
11356        editor
11357            .insert_snippet(&insertion_ranges, snippet, window, cx)
11358            .unwrap();
11359
11360        assert_state(editor, cx, "fn «» = value;•");
11361
11362        assert!(
11363            editor.context_menu_visible(),
11364            "Context menu should be visible for placeholder choices"
11365        );
11366
11367        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11368
11369        assert_state(editor, cx, "fn  = «valueˇ»;•");
11370
11371        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11372
11373        assert_state(editor, cx, "fn «» = value;•");
11374
11375        assert!(
11376            editor.context_menu_visible(),
11377            "Context menu should be visible again after returning to first tabstop"
11378        );
11379
11380        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11381
11382        assert_state(editor, cx, "fn «» = value;•");
11383    });
11384}
11385
11386#[gpui::test]
11387async fn test_snippets(cx: &mut TestAppContext) {
11388    init_test(cx, |_| {});
11389
11390    let mut cx = EditorTestContext::new(cx).await;
11391
11392    cx.set_state(indoc! {"
11393        a.ˇ b
11394        a.ˇ b
11395        a.ˇ b
11396    "});
11397
11398    cx.update_editor(|editor, window, cx| {
11399        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11400        let insertion_ranges = editor
11401            .selections
11402            .all(&editor.display_snapshot(cx))
11403            .iter()
11404            .map(|s| s.range())
11405            .collect::<Vec<_>>();
11406        editor
11407            .insert_snippet(&insertion_ranges, snippet, window, cx)
11408            .unwrap();
11409    });
11410
11411    cx.assert_editor_state(indoc! {"
11412        a.f(«oneˇ», two, «threeˇ») b
11413        a.f(«oneˇ», two, «threeˇ») b
11414        a.f(«oneˇ», two, «threeˇ») b
11415    "});
11416
11417    // Can't move earlier than the first tab stop
11418    cx.update_editor(|editor, window, cx| {
11419        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11420    });
11421    cx.assert_editor_state(indoc! {"
11422        a.f(«oneˇ», two, «threeˇ») b
11423        a.f(«oneˇ», two, «threeˇ») b
11424        a.f(«oneˇ», two, «threeˇ») b
11425    "});
11426
11427    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11428    cx.assert_editor_state(indoc! {"
11429        a.f(one, «twoˇ», three) b
11430        a.f(one, «twoˇ», three) b
11431        a.f(one, «twoˇ», three) b
11432    "});
11433
11434    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11435    cx.assert_editor_state(indoc! {"
11436        a.f(«oneˇ», two, «threeˇ») b
11437        a.f(«oneˇ», two, «threeˇ») b
11438        a.f(«oneˇ», two, «threeˇ») b
11439    "});
11440
11441    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11442    cx.assert_editor_state(indoc! {"
11443        a.f(one, «twoˇ», three) b
11444        a.f(one, «twoˇ», three) b
11445        a.f(one, «twoˇ», three) b
11446    "});
11447    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11448    cx.assert_editor_state(indoc! {"
11449        a.f(one, two, three)ˇ b
11450        a.f(one, two, three)ˇ b
11451        a.f(one, two, three)ˇ b
11452    "});
11453
11454    // As soon as the last tab stop is reached, snippet state is gone
11455    cx.update_editor(|editor, window, cx| {
11456        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11457    });
11458    cx.assert_editor_state(indoc! {"
11459        a.f(one, two, three)ˇ b
11460        a.f(one, two, three)ˇ b
11461        a.f(one, two, three)ˇ b
11462    "});
11463}
11464
11465#[gpui::test]
11466async fn test_snippet_indentation(cx: &mut TestAppContext) {
11467    init_test(cx, |_| {});
11468
11469    let mut cx = EditorTestContext::new(cx).await;
11470
11471    cx.update_editor(|editor, window, cx| {
11472        let snippet = Snippet::parse(indoc! {"
11473            /*
11474             * Multiline comment with leading indentation
11475             *
11476             * $1
11477             */
11478            $0"})
11479        .unwrap();
11480        let insertion_ranges = editor
11481            .selections
11482            .all(&editor.display_snapshot(cx))
11483            .iter()
11484            .map(|s| s.range())
11485            .collect::<Vec<_>>();
11486        editor
11487            .insert_snippet(&insertion_ranges, snippet, window, cx)
11488            .unwrap();
11489    });
11490
11491    cx.assert_editor_state(indoc! {"
11492        /*
11493         * Multiline comment with leading indentation
11494         *
11495         * ˇ
11496         */
11497    "});
11498
11499    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11500    cx.assert_editor_state(indoc! {"
11501        /*
11502         * Multiline comment with leading indentation
11503         *
11504         *•
11505         */
11506        ˇ"});
11507}
11508
11509#[gpui::test]
11510async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11511    init_test(cx, |_| {});
11512
11513    let mut cx = EditorTestContext::new(cx).await;
11514    cx.update_editor(|editor, _, cx| {
11515        editor.project().unwrap().update(cx, |project, cx| {
11516            project.snippets().update(cx, |snippets, _cx| {
11517                let snippet = project::snippet_provider::Snippet {
11518                    prefix: vec!["multi word".to_string()],
11519                    body: "this is many words".to_string(),
11520                    description: Some("description".to_string()),
11521                    name: "multi-word snippet test".to_string(),
11522                };
11523                snippets.add_snippet_for_test(
11524                    None,
11525                    PathBuf::from("test_snippets.json"),
11526                    vec![Arc::new(snippet)],
11527                );
11528            });
11529        })
11530    });
11531
11532    for (input_to_simulate, should_match_snippet) in [
11533        ("m", true),
11534        ("m ", true),
11535        ("m w", true),
11536        ("aa m w", true),
11537        ("aa m g", false),
11538    ] {
11539        cx.set_state("ˇ");
11540        cx.simulate_input(input_to_simulate); // fails correctly
11541
11542        cx.update_editor(|editor, _, _| {
11543            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
11544            else {
11545                assert!(!should_match_snippet); // no completions! don't even show the menu
11546                return;
11547            };
11548            assert!(context_menu.visible());
11549            let completions = context_menu.completions.borrow();
11550
11551            assert_eq!(!completions.is_empty(), should_match_snippet);
11552        });
11553    }
11554}
11555
11556#[gpui::test]
11557async fn test_document_format_during_save(cx: &mut TestAppContext) {
11558    init_test(cx, |_| {});
11559
11560    let fs = FakeFs::new(cx.executor());
11561    fs.insert_file(path!("/file.rs"), Default::default()).await;
11562
11563    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11564
11565    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11566    language_registry.add(rust_lang());
11567    let mut fake_servers = language_registry.register_fake_lsp(
11568        "Rust",
11569        FakeLspAdapter {
11570            capabilities: lsp::ServerCapabilities {
11571                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11572                ..Default::default()
11573            },
11574            ..Default::default()
11575        },
11576    );
11577
11578    let buffer = project
11579        .update(cx, |project, cx| {
11580            project.open_local_buffer(path!("/file.rs"), cx)
11581        })
11582        .await
11583        .unwrap();
11584
11585    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11586    let (editor, cx) = cx.add_window_view(|window, cx| {
11587        build_editor_with_project(project.clone(), buffer, window, cx)
11588    });
11589    editor.update_in(cx, |editor, window, cx| {
11590        editor.set_text("one\ntwo\nthree\n", window, cx)
11591    });
11592    assert!(cx.read(|cx| editor.is_dirty(cx)));
11593
11594    cx.executor().start_waiting();
11595    let fake_server = fake_servers.next().await.unwrap();
11596
11597    {
11598        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11599            move |params, _| async move {
11600                assert_eq!(
11601                    params.text_document.uri,
11602                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11603                );
11604                assert_eq!(params.options.tab_size, 4);
11605                Ok(Some(vec![lsp::TextEdit::new(
11606                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11607                    ", ".to_string(),
11608                )]))
11609            },
11610        );
11611        let save = editor
11612            .update_in(cx, |editor, window, cx| {
11613                editor.save(
11614                    SaveOptions {
11615                        format: true,
11616                        autosave: false,
11617                    },
11618                    project.clone(),
11619                    window,
11620                    cx,
11621                )
11622            })
11623            .unwrap();
11624        cx.executor().start_waiting();
11625        save.await;
11626
11627        assert_eq!(
11628            editor.update(cx, |editor, cx| editor.text(cx)),
11629            "one, two\nthree\n"
11630        );
11631        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11632    }
11633
11634    {
11635        editor.update_in(cx, |editor, window, cx| {
11636            editor.set_text("one\ntwo\nthree\n", window, cx)
11637        });
11638        assert!(cx.read(|cx| editor.is_dirty(cx)));
11639
11640        // Ensure we can still save even if formatting hangs.
11641        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11642            move |params, _| async move {
11643                assert_eq!(
11644                    params.text_document.uri,
11645                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11646                );
11647                futures::future::pending::<()>().await;
11648                unreachable!()
11649            },
11650        );
11651        let save = editor
11652            .update_in(cx, |editor, window, cx| {
11653                editor.save(
11654                    SaveOptions {
11655                        format: true,
11656                        autosave: false,
11657                    },
11658                    project.clone(),
11659                    window,
11660                    cx,
11661                )
11662            })
11663            .unwrap();
11664        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11665        cx.executor().start_waiting();
11666        save.await;
11667        assert_eq!(
11668            editor.update(cx, |editor, cx| editor.text(cx)),
11669            "one\ntwo\nthree\n"
11670        );
11671    }
11672
11673    // Set rust language override and assert overridden tabsize is sent to language server
11674    update_test_language_settings(cx, |settings| {
11675        settings.languages.0.insert(
11676            "Rust".into(),
11677            LanguageSettingsContent {
11678                tab_size: NonZeroU32::new(8),
11679                ..Default::default()
11680            },
11681        );
11682    });
11683
11684    {
11685        editor.update_in(cx, |editor, window, cx| {
11686            editor.set_text("somehting_new\n", window, cx)
11687        });
11688        assert!(cx.read(|cx| editor.is_dirty(cx)));
11689        let _formatting_request_signal = fake_server
11690            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11691                assert_eq!(
11692                    params.text_document.uri,
11693                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11694                );
11695                assert_eq!(params.options.tab_size, 8);
11696                Ok(Some(vec![]))
11697            });
11698        let save = editor
11699            .update_in(cx, |editor, window, cx| {
11700                editor.save(
11701                    SaveOptions {
11702                        format: true,
11703                        autosave: false,
11704                    },
11705                    project.clone(),
11706                    window,
11707                    cx,
11708                )
11709            })
11710            .unwrap();
11711        cx.executor().start_waiting();
11712        save.await;
11713    }
11714}
11715
11716#[gpui::test]
11717async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11718    init_test(cx, |settings| {
11719        settings.defaults.ensure_final_newline_on_save = Some(false);
11720    });
11721
11722    let fs = FakeFs::new(cx.executor());
11723    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11724
11725    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11726
11727    let buffer = project
11728        .update(cx, |project, cx| {
11729            project.open_local_buffer(path!("/file.txt"), cx)
11730        })
11731        .await
11732        .unwrap();
11733
11734    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11735    let (editor, cx) = cx.add_window_view(|window, cx| {
11736        build_editor_with_project(project.clone(), buffer, window, cx)
11737    });
11738    editor.update_in(cx, |editor, window, cx| {
11739        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11740            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
11741        });
11742    });
11743    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11744
11745    editor.update_in(cx, |editor, window, cx| {
11746        editor.handle_input("\n", window, cx)
11747    });
11748    cx.run_until_parked();
11749    save(&editor, &project, cx).await;
11750    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11751
11752    editor.update_in(cx, |editor, window, cx| {
11753        editor.undo(&Default::default(), window, cx);
11754    });
11755    save(&editor, &project, cx).await;
11756    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11757
11758    editor.update_in(cx, |editor, window, cx| {
11759        editor.redo(&Default::default(), window, cx);
11760    });
11761    cx.run_until_parked();
11762    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11763
11764    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11765        let save = editor
11766            .update_in(cx, |editor, window, cx| {
11767                editor.save(
11768                    SaveOptions {
11769                        format: true,
11770                        autosave: false,
11771                    },
11772                    project.clone(),
11773                    window,
11774                    cx,
11775                )
11776            })
11777            .unwrap();
11778        cx.executor().start_waiting();
11779        save.await;
11780        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11781    }
11782}
11783
11784#[gpui::test]
11785async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11786    init_test(cx, |_| {});
11787
11788    let cols = 4;
11789    let rows = 10;
11790    let sample_text_1 = sample_text(rows, cols, 'a');
11791    assert_eq!(
11792        sample_text_1,
11793        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11794    );
11795    let sample_text_2 = sample_text(rows, cols, 'l');
11796    assert_eq!(
11797        sample_text_2,
11798        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11799    );
11800    let sample_text_3 = sample_text(rows, cols, 'v');
11801    assert_eq!(
11802        sample_text_3,
11803        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11804    );
11805
11806    let fs = FakeFs::new(cx.executor());
11807    fs.insert_tree(
11808        path!("/a"),
11809        json!({
11810            "main.rs": sample_text_1,
11811            "other.rs": sample_text_2,
11812            "lib.rs": sample_text_3,
11813        }),
11814    )
11815    .await;
11816
11817    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11818    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11819    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11820
11821    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11822    language_registry.add(rust_lang());
11823    let mut fake_servers = language_registry.register_fake_lsp(
11824        "Rust",
11825        FakeLspAdapter {
11826            capabilities: lsp::ServerCapabilities {
11827                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11828                ..Default::default()
11829            },
11830            ..Default::default()
11831        },
11832    );
11833
11834    let worktree = project.update(cx, |project, cx| {
11835        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11836        assert_eq!(worktrees.len(), 1);
11837        worktrees.pop().unwrap()
11838    });
11839    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11840
11841    let buffer_1 = project
11842        .update(cx, |project, cx| {
11843            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11844        })
11845        .await
11846        .unwrap();
11847    let buffer_2 = project
11848        .update(cx, |project, cx| {
11849            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11850        })
11851        .await
11852        .unwrap();
11853    let buffer_3 = project
11854        .update(cx, |project, cx| {
11855            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11856        })
11857        .await
11858        .unwrap();
11859
11860    let multi_buffer = cx.new(|cx| {
11861        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11862        multi_buffer.push_excerpts(
11863            buffer_1.clone(),
11864            [
11865                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11866                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11867                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11868            ],
11869            cx,
11870        );
11871        multi_buffer.push_excerpts(
11872            buffer_2.clone(),
11873            [
11874                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11875                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11876                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11877            ],
11878            cx,
11879        );
11880        multi_buffer.push_excerpts(
11881            buffer_3.clone(),
11882            [
11883                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11884                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11885                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11886            ],
11887            cx,
11888        );
11889        multi_buffer
11890    });
11891    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11892        Editor::new(
11893            EditorMode::full(),
11894            multi_buffer,
11895            Some(project.clone()),
11896            window,
11897            cx,
11898        )
11899    });
11900
11901    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11902        editor.change_selections(
11903            SelectionEffects::scroll(Autoscroll::Next),
11904            window,
11905            cx,
11906            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
11907        );
11908        editor.insert("|one|two|three|", window, cx);
11909    });
11910    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11911    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11912        editor.change_selections(
11913            SelectionEffects::scroll(Autoscroll::Next),
11914            window,
11915            cx,
11916            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
11917        );
11918        editor.insert("|four|five|six|", window, cx);
11919    });
11920    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11921
11922    // First two buffers should be edited, but not the third one.
11923    assert_eq!(
11924        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11925        "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}",
11926    );
11927    buffer_1.update(cx, |buffer, _| {
11928        assert!(buffer.is_dirty());
11929        assert_eq!(
11930            buffer.text(),
11931            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11932        )
11933    });
11934    buffer_2.update(cx, |buffer, _| {
11935        assert!(buffer.is_dirty());
11936        assert_eq!(
11937            buffer.text(),
11938            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11939        )
11940    });
11941    buffer_3.update(cx, |buffer, _| {
11942        assert!(!buffer.is_dirty());
11943        assert_eq!(buffer.text(), sample_text_3,)
11944    });
11945    cx.executor().run_until_parked();
11946
11947    cx.executor().start_waiting();
11948    let save = multi_buffer_editor
11949        .update_in(cx, |editor, window, cx| {
11950            editor.save(
11951                SaveOptions {
11952                    format: true,
11953                    autosave: false,
11954                },
11955                project.clone(),
11956                window,
11957                cx,
11958            )
11959        })
11960        .unwrap();
11961
11962    let fake_server = fake_servers.next().await.unwrap();
11963    fake_server
11964        .server
11965        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11966            Ok(Some(vec![lsp::TextEdit::new(
11967                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11968                format!("[{} formatted]", params.text_document.uri),
11969            )]))
11970        })
11971        .detach();
11972    save.await;
11973
11974    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11975    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11976    assert_eq!(
11977        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11978        uri!(
11979            "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}"
11980        ),
11981    );
11982    buffer_1.update(cx, |buffer, _| {
11983        assert!(!buffer.is_dirty());
11984        assert_eq!(
11985            buffer.text(),
11986            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11987        )
11988    });
11989    buffer_2.update(cx, |buffer, _| {
11990        assert!(!buffer.is_dirty());
11991        assert_eq!(
11992            buffer.text(),
11993            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11994        )
11995    });
11996    buffer_3.update(cx, |buffer, _| {
11997        assert!(!buffer.is_dirty());
11998        assert_eq!(buffer.text(), sample_text_3,)
11999    });
12000}
12001
12002#[gpui::test]
12003async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12004    init_test(cx, |_| {});
12005
12006    let fs = FakeFs::new(cx.executor());
12007    fs.insert_tree(
12008        path!("/dir"),
12009        json!({
12010            "file1.rs": "fn main() { println!(\"hello\"); }",
12011            "file2.rs": "fn test() { println!(\"test\"); }",
12012            "file3.rs": "fn other() { println!(\"other\"); }\n",
12013        }),
12014    )
12015    .await;
12016
12017    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12018    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12019    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12020
12021    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12022    language_registry.add(rust_lang());
12023
12024    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12025    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12026
12027    // Open three buffers
12028    let buffer_1 = project
12029        .update(cx, |project, cx| {
12030            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12031        })
12032        .await
12033        .unwrap();
12034    let buffer_2 = project
12035        .update(cx, |project, cx| {
12036            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12037        })
12038        .await
12039        .unwrap();
12040    let buffer_3 = project
12041        .update(cx, |project, cx| {
12042            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12043        })
12044        .await
12045        .unwrap();
12046
12047    // Create a multi-buffer with all three buffers
12048    let multi_buffer = cx.new(|cx| {
12049        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12050        multi_buffer.push_excerpts(
12051            buffer_1.clone(),
12052            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12053            cx,
12054        );
12055        multi_buffer.push_excerpts(
12056            buffer_2.clone(),
12057            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12058            cx,
12059        );
12060        multi_buffer.push_excerpts(
12061            buffer_3.clone(),
12062            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12063            cx,
12064        );
12065        multi_buffer
12066    });
12067
12068    let editor = cx.new_window_entity(|window, cx| {
12069        Editor::new(
12070            EditorMode::full(),
12071            multi_buffer,
12072            Some(project.clone()),
12073            window,
12074            cx,
12075        )
12076    });
12077
12078    // Edit only the first buffer
12079    editor.update_in(cx, |editor, window, cx| {
12080        editor.change_selections(
12081            SelectionEffects::scroll(Autoscroll::Next),
12082            window,
12083            cx,
12084            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12085        );
12086        editor.insert("// edited", window, cx);
12087    });
12088
12089    // Verify that only buffer 1 is dirty
12090    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12091    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12092    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12093
12094    // Get write counts after file creation (files were created with initial content)
12095    // We expect each file to have been written once during creation
12096    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12097    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12098    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12099
12100    // Perform autosave
12101    let save_task = editor.update_in(cx, |editor, window, cx| {
12102        editor.save(
12103            SaveOptions {
12104                format: true,
12105                autosave: true,
12106            },
12107            project.clone(),
12108            window,
12109            cx,
12110        )
12111    });
12112    save_task.await.unwrap();
12113
12114    // Only the dirty buffer should have been saved
12115    assert_eq!(
12116        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12117        1,
12118        "Buffer 1 was dirty, so it should have been written once during autosave"
12119    );
12120    assert_eq!(
12121        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12122        0,
12123        "Buffer 2 was clean, so it should not have been written during autosave"
12124    );
12125    assert_eq!(
12126        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12127        0,
12128        "Buffer 3 was clean, so it should not have been written during autosave"
12129    );
12130
12131    // Verify buffer states after autosave
12132    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12133    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12134    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12135
12136    // Now perform a manual save (format = true)
12137    let save_task = editor.update_in(cx, |editor, window, cx| {
12138        editor.save(
12139            SaveOptions {
12140                format: true,
12141                autosave: false,
12142            },
12143            project.clone(),
12144            window,
12145            cx,
12146        )
12147    });
12148    save_task.await.unwrap();
12149
12150    // During manual save, clean buffers don't get written to disk
12151    // They just get did_save called for language server notifications
12152    assert_eq!(
12153        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12154        1,
12155        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12156    );
12157    assert_eq!(
12158        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12159        0,
12160        "Buffer 2 should not have been written at all"
12161    );
12162    assert_eq!(
12163        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12164        0,
12165        "Buffer 3 should not have been written at all"
12166    );
12167}
12168
12169async fn setup_range_format_test(
12170    cx: &mut TestAppContext,
12171) -> (
12172    Entity<Project>,
12173    Entity<Editor>,
12174    &mut gpui::VisualTestContext,
12175    lsp::FakeLanguageServer,
12176) {
12177    init_test(cx, |_| {});
12178
12179    let fs = FakeFs::new(cx.executor());
12180    fs.insert_file(path!("/file.rs"), Default::default()).await;
12181
12182    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12183
12184    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12185    language_registry.add(rust_lang());
12186    let mut fake_servers = language_registry.register_fake_lsp(
12187        "Rust",
12188        FakeLspAdapter {
12189            capabilities: lsp::ServerCapabilities {
12190                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12191                ..lsp::ServerCapabilities::default()
12192            },
12193            ..FakeLspAdapter::default()
12194        },
12195    );
12196
12197    let buffer = project
12198        .update(cx, |project, cx| {
12199            project.open_local_buffer(path!("/file.rs"), cx)
12200        })
12201        .await
12202        .unwrap();
12203
12204    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12205    let (editor, cx) = cx.add_window_view(|window, cx| {
12206        build_editor_with_project(project.clone(), buffer, window, cx)
12207    });
12208
12209    cx.executor().start_waiting();
12210    let fake_server = fake_servers.next().await.unwrap();
12211
12212    (project, editor, cx, fake_server)
12213}
12214
12215#[gpui::test]
12216async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12217    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12218
12219    editor.update_in(cx, |editor, window, cx| {
12220        editor.set_text("one\ntwo\nthree\n", window, cx)
12221    });
12222    assert!(cx.read(|cx| editor.is_dirty(cx)));
12223
12224    let save = editor
12225        .update_in(cx, |editor, window, cx| {
12226            editor.save(
12227                SaveOptions {
12228                    format: true,
12229                    autosave: false,
12230                },
12231                project.clone(),
12232                window,
12233                cx,
12234            )
12235        })
12236        .unwrap();
12237    fake_server
12238        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12239            assert_eq!(
12240                params.text_document.uri,
12241                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12242            );
12243            assert_eq!(params.options.tab_size, 4);
12244            Ok(Some(vec![lsp::TextEdit::new(
12245                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12246                ", ".to_string(),
12247            )]))
12248        })
12249        .next()
12250        .await;
12251    cx.executor().start_waiting();
12252    save.await;
12253    assert_eq!(
12254        editor.update(cx, |editor, cx| editor.text(cx)),
12255        "one, two\nthree\n"
12256    );
12257    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12258}
12259
12260#[gpui::test]
12261async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12262    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12263
12264    editor.update_in(cx, |editor, window, cx| {
12265        editor.set_text("one\ntwo\nthree\n", window, cx)
12266    });
12267    assert!(cx.read(|cx| editor.is_dirty(cx)));
12268
12269    // Test that save still works when formatting hangs
12270    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12271        move |params, _| async move {
12272            assert_eq!(
12273                params.text_document.uri,
12274                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12275            );
12276            futures::future::pending::<()>().await;
12277            unreachable!()
12278        },
12279    );
12280    let save = editor
12281        .update_in(cx, |editor, window, cx| {
12282            editor.save(
12283                SaveOptions {
12284                    format: true,
12285                    autosave: false,
12286                },
12287                project.clone(),
12288                window,
12289                cx,
12290            )
12291        })
12292        .unwrap();
12293    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12294    cx.executor().start_waiting();
12295    save.await;
12296    assert_eq!(
12297        editor.update(cx, |editor, cx| editor.text(cx)),
12298        "one\ntwo\nthree\n"
12299    );
12300    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12301}
12302
12303#[gpui::test]
12304async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12305    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12306
12307    // Buffer starts clean, no formatting should be requested
12308    let save = editor
12309        .update_in(cx, |editor, window, cx| {
12310            editor.save(
12311                SaveOptions {
12312                    format: false,
12313                    autosave: false,
12314                },
12315                project.clone(),
12316                window,
12317                cx,
12318            )
12319        })
12320        .unwrap();
12321    let _pending_format_request = fake_server
12322        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12323            panic!("Should not be invoked");
12324        })
12325        .next();
12326    cx.executor().start_waiting();
12327    save.await;
12328    cx.run_until_parked();
12329}
12330
12331#[gpui::test]
12332async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12333    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12334
12335    // Set Rust language override and assert overridden tabsize is sent to language server
12336    update_test_language_settings(cx, |settings| {
12337        settings.languages.0.insert(
12338            "Rust".into(),
12339            LanguageSettingsContent {
12340                tab_size: NonZeroU32::new(8),
12341                ..Default::default()
12342            },
12343        );
12344    });
12345
12346    editor.update_in(cx, |editor, window, cx| {
12347        editor.set_text("something_new\n", window, cx)
12348    });
12349    assert!(cx.read(|cx| editor.is_dirty(cx)));
12350    let save = editor
12351        .update_in(cx, |editor, window, cx| {
12352            editor.save(
12353                SaveOptions {
12354                    format: true,
12355                    autosave: false,
12356                },
12357                project.clone(),
12358                window,
12359                cx,
12360            )
12361        })
12362        .unwrap();
12363    fake_server
12364        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12365            assert_eq!(
12366                params.text_document.uri,
12367                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12368            );
12369            assert_eq!(params.options.tab_size, 8);
12370            Ok(Some(Vec::new()))
12371        })
12372        .next()
12373        .await;
12374    save.await;
12375}
12376
12377#[gpui::test]
12378async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12379    init_test(cx, |settings| {
12380        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12381            settings::LanguageServerFormatterSpecifier::Current,
12382        )))
12383    });
12384
12385    let fs = FakeFs::new(cx.executor());
12386    fs.insert_file(path!("/file.rs"), Default::default()).await;
12387
12388    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12389
12390    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12391    language_registry.add(Arc::new(Language::new(
12392        LanguageConfig {
12393            name: "Rust".into(),
12394            matcher: LanguageMatcher {
12395                path_suffixes: vec!["rs".to_string()],
12396                ..Default::default()
12397            },
12398            ..LanguageConfig::default()
12399        },
12400        Some(tree_sitter_rust::LANGUAGE.into()),
12401    )));
12402    update_test_language_settings(cx, |settings| {
12403        // Enable Prettier formatting for the same buffer, and ensure
12404        // LSP is called instead of Prettier.
12405        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12406    });
12407    let mut fake_servers = language_registry.register_fake_lsp(
12408        "Rust",
12409        FakeLspAdapter {
12410            capabilities: lsp::ServerCapabilities {
12411                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12412                ..Default::default()
12413            },
12414            ..Default::default()
12415        },
12416    );
12417
12418    let buffer = project
12419        .update(cx, |project, cx| {
12420            project.open_local_buffer(path!("/file.rs"), cx)
12421        })
12422        .await
12423        .unwrap();
12424
12425    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12426    let (editor, cx) = cx.add_window_view(|window, cx| {
12427        build_editor_with_project(project.clone(), buffer, window, cx)
12428    });
12429    editor.update_in(cx, |editor, window, cx| {
12430        editor.set_text("one\ntwo\nthree\n", window, cx)
12431    });
12432
12433    cx.executor().start_waiting();
12434    let fake_server = fake_servers.next().await.unwrap();
12435
12436    let format = editor
12437        .update_in(cx, |editor, window, cx| {
12438            editor.perform_format(
12439                project.clone(),
12440                FormatTrigger::Manual,
12441                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12442                window,
12443                cx,
12444            )
12445        })
12446        .unwrap();
12447    fake_server
12448        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12449            assert_eq!(
12450                params.text_document.uri,
12451                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12452            );
12453            assert_eq!(params.options.tab_size, 4);
12454            Ok(Some(vec![lsp::TextEdit::new(
12455                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12456                ", ".to_string(),
12457            )]))
12458        })
12459        .next()
12460        .await;
12461    cx.executor().start_waiting();
12462    format.await;
12463    assert_eq!(
12464        editor.update(cx, |editor, cx| editor.text(cx)),
12465        "one, two\nthree\n"
12466    );
12467
12468    editor.update_in(cx, |editor, window, cx| {
12469        editor.set_text("one\ntwo\nthree\n", window, cx)
12470    });
12471    // Ensure we don't lock if formatting hangs.
12472    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12473        move |params, _| async move {
12474            assert_eq!(
12475                params.text_document.uri,
12476                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12477            );
12478            futures::future::pending::<()>().await;
12479            unreachable!()
12480        },
12481    );
12482    let format = editor
12483        .update_in(cx, |editor, window, cx| {
12484            editor.perform_format(
12485                project,
12486                FormatTrigger::Manual,
12487                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12488                window,
12489                cx,
12490            )
12491        })
12492        .unwrap();
12493    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12494    cx.executor().start_waiting();
12495    format.await;
12496    assert_eq!(
12497        editor.update(cx, |editor, cx| editor.text(cx)),
12498        "one\ntwo\nthree\n"
12499    );
12500}
12501
12502#[gpui::test]
12503async fn test_multiple_formatters(cx: &mut TestAppContext) {
12504    init_test(cx, |settings| {
12505        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12506        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12507            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12508            Formatter::CodeAction("code-action-1".into()),
12509            Formatter::CodeAction("code-action-2".into()),
12510        ]))
12511    });
12512
12513    let fs = FakeFs::new(cx.executor());
12514    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12515        .await;
12516
12517    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12518    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12519    language_registry.add(rust_lang());
12520
12521    let mut fake_servers = language_registry.register_fake_lsp(
12522        "Rust",
12523        FakeLspAdapter {
12524            capabilities: lsp::ServerCapabilities {
12525                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12526                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12527                    commands: vec!["the-command-for-code-action-1".into()],
12528                    ..Default::default()
12529                }),
12530                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12531                ..Default::default()
12532            },
12533            ..Default::default()
12534        },
12535    );
12536
12537    let buffer = project
12538        .update(cx, |project, cx| {
12539            project.open_local_buffer(path!("/file.rs"), cx)
12540        })
12541        .await
12542        .unwrap();
12543
12544    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12545    let (editor, cx) = cx.add_window_view(|window, cx| {
12546        build_editor_with_project(project.clone(), buffer, window, cx)
12547    });
12548
12549    cx.executor().start_waiting();
12550
12551    let fake_server = fake_servers.next().await.unwrap();
12552    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12553        move |_params, _| async move {
12554            Ok(Some(vec![lsp::TextEdit::new(
12555                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12556                "applied-formatting\n".to_string(),
12557            )]))
12558        },
12559    );
12560    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12561        move |params, _| async move {
12562            let requested_code_actions = params.context.only.expect("Expected code action request");
12563            assert_eq!(requested_code_actions.len(), 1);
12564
12565            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12566            let code_action = match requested_code_actions[0].as_str() {
12567                "code-action-1" => lsp::CodeAction {
12568                    kind: Some("code-action-1".into()),
12569                    edit: Some(lsp::WorkspaceEdit::new(
12570                        [(
12571                            uri,
12572                            vec![lsp::TextEdit::new(
12573                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12574                                "applied-code-action-1-edit\n".to_string(),
12575                            )],
12576                        )]
12577                        .into_iter()
12578                        .collect(),
12579                    )),
12580                    command: Some(lsp::Command {
12581                        command: "the-command-for-code-action-1".into(),
12582                        ..Default::default()
12583                    }),
12584                    ..Default::default()
12585                },
12586                "code-action-2" => lsp::CodeAction {
12587                    kind: Some("code-action-2".into()),
12588                    edit: Some(lsp::WorkspaceEdit::new(
12589                        [(
12590                            uri,
12591                            vec![lsp::TextEdit::new(
12592                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12593                                "applied-code-action-2-edit\n".to_string(),
12594                            )],
12595                        )]
12596                        .into_iter()
12597                        .collect(),
12598                    )),
12599                    ..Default::default()
12600                },
12601                req => panic!("Unexpected code action request: {:?}", req),
12602            };
12603            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12604                code_action,
12605            )]))
12606        },
12607    );
12608
12609    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12610        move |params, _| async move { Ok(params) }
12611    });
12612
12613    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12614    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12615        let fake = fake_server.clone();
12616        let lock = command_lock.clone();
12617        move |params, _| {
12618            assert_eq!(params.command, "the-command-for-code-action-1");
12619            let fake = fake.clone();
12620            let lock = lock.clone();
12621            async move {
12622                lock.lock().await;
12623                fake.server
12624                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12625                        label: None,
12626                        edit: lsp::WorkspaceEdit {
12627                            changes: Some(
12628                                [(
12629                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12630                                    vec![lsp::TextEdit {
12631                                        range: lsp::Range::new(
12632                                            lsp::Position::new(0, 0),
12633                                            lsp::Position::new(0, 0),
12634                                        ),
12635                                        new_text: "applied-code-action-1-command\n".into(),
12636                                    }],
12637                                )]
12638                                .into_iter()
12639                                .collect(),
12640                            ),
12641                            ..Default::default()
12642                        },
12643                    })
12644                    .await
12645                    .into_response()
12646                    .unwrap();
12647                Ok(Some(json!(null)))
12648            }
12649        }
12650    });
12651
12652    cx.executor().start_waiting();
12653    editor
12654        .update_in(cx, |editor, window, cx| {
12655            editor.perform_format(
12656                project.clone(),
12657                FormatTrigger::Manual,
12658                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12659                window,
12660                cx,
12661            )
12662        })
12663        .unwrap()
12664        .await;
12665    editor.update(cx, |editor, cx| {
12666        assert_eq!(
12667            editor.text(cx),
12668            r#"
12669                applied-code-action-2-edit
12670                applied-code-action-1-command
12671                applied-code-action-1-edit
12672                applied-formatting
12673                one
12674                two
12675                three
12676            "#
12677            .unindent()
12678        );
12679    });
12680
12681    editor.update_in(cx, |editor, window, cx| {
12682        editor.undo(&Default::default(), window, cx);
12683        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12684    });
12685
12686    // Perform a manual edit while waiting for an LSP command
12687    // that's being run as part of a formatting code action.
12688    let lock_guard = command_lock.lock().await;
12689    let format = editor
12690        .update_in(cx, |editor, window, cx| {
12691            editor.perform_format(
12692                project.clone(),
12693                FormatTrigger::Manual,
12694                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12695                window,
12696                cx,
12697            )
12698        })
12699        .unwrap();
12700    cx.run_until_parked();
12701    editor.update(cx, |editor, cx| {
12702        assert_eq!(
12703            editor.text(cx),
12704            r#"
12705                applied-code-action-1-edit
12706                applied-formatting
12707                one
12708                two
12709                three
12710            "#
12711            .unindent()
12712        );
12713
12714        editor.buffer.update(cx, |buffer, cx| {
12715            let ix = buffer.len(cx);
12716            buffer.edit([(ix..ix, "edited\n")], None, cx);
12717        });
12718    });
12719
12720    // Allow the LSP command to proceed. Because the buffer was edited,
12721    // the second code action will not be run.
12722    drop(lock_guard);
12723    format.await;
12724    editor.update_in(cx, |editor, window, cx| {
12725        assert_eq!(
12726            editor.text(cx),
12727            r#"
12728                applied-code-action-1-command
12729                applied-code-action-1-edit
12730                applied-formatting
12731                one
12732                two
12733                three
12734                edited
12735            "#
12736            .unindent()
12737        );
12738
12739        // The manual edit is undone first, because it is the last thing the user did
12740        // (even though the command completed afterwards).
12741        editor.undo(&Default::default(), window, cx);
12742        assert_eq!(
12743            editor.text(cx),
12744            r#"
12745                applied-code-action-1-command
12746                applied-code-action-1-edit
12747                applied-formatting
12748                one
12749                two
12750                three
12751            "#
12752            .unindent()
12753        );
12754
12755        // All the formatting (including the command, which completed after the manual edit)
12756        // is undone together.
12757        editor.undo(&Default::default(), window, cx);
12758        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12759    });
12760}
12761
12762#[gpui::test]
12763async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12764    init_test(cx, |settings| {
12765        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12766            settings::LanguageServerFormatterSpecifier::Current,
12767        )]))
12768    });
12769
12770    let fs = FakeFs::new(cx.executor());
12771    fs.insert_file(path!("/file.ts"), Default::default()).await;
12772
12773    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12774
12775    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12776    language_registry.add(Arc::new(Language::new(
12777        LanguageConfig {
12778            name: "TypeScript".into(),
12779            matcher: LanguageMatcher {
12780                path_suffixes: vec!["ts".to_string()],
12781                ..Default::default()
12782            },
12783            ..LanguageConfig::default()
12784        },
12785        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12786    )));
12787    update_test_language_settings(cx, |settings| {
12788        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12789    });
12790    let mut fake_servers = language_registry.register_fake_lsp(
12791        "TypeScript",
12792        FakeLspAdapter {
12793            capabilities: lsp::ServerCapabilities {
12794                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12795                ..Default::default()
12796            },
12797            ..Default::default()
12798        },
12799    );
12800
12801    let buffer = project
12802        .update(cx, |project, cx| {
12803            project.open_local_buffer(path!("/file.ts"), cx)
12804        })
12805        .await
12806        .unwrap();
12807
12808    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12809    let (editor, cx) = cx.add_window_view(|window, cx| {
12810        build_editor_with_project(project.clone(), buffer, window, cx)
12811    });
12812    editor.update_in(cx, |editor, window, cx| {
12813        editor.set_text(
12814            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12815            window,
12816            cx,
12817        )
12818    });
12819
12820    cx.executor().start_waiting();
12821    let fake_server = fake_servers.next().await.unwrap();
12822
12823    let format = editor
12824        .update_in(cx, |editor, window, cx| {
12825            editor.perform_code_action_kind(
12826                project.clone(),
12827                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12828                window,
12829                cx,
12830            )
12831        })
12832        .unwrap();
12833    fake_server
12834        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12835            assert_eq!(
12836                params.text_document.uri,
12837                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12838            );
12839            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12840                lsp::CodeAction {
12841                    title: "Organize Imports".to_string(),
12842                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12843                    edit: Some(lsp::WorkspaceEdit {
12844                        changes: Some(
12845                            [(
12846                                params.text_document.uri.clone(),
12847                                vec![lsp::TextEdit::new(
12848                                    lsp::Range::new(
12849                                        lsp::Position::new(1, 0),
12850                                        lsp::Position::new(2, 0),
12851                                    ),
12852                                    "".to_string(),
12853                                )],
12854                            )]
12855                            .into_iter()
12856                            .collect(),
12857                        ),
12858                        ..Default::default()
12859                    }),
12860                    ..Default::default()
12861                },
12862            )]))
12863        })
12864        .next()
12865        .await;
12866    cx.executor().start_waiting();
12867    format.await;
12868    assert_eq!(
12869        editor.update(cx, |editor, cx| editor.text(cx)),
12870        "import { a } from 'module';\n\nconst x = a;\n"
12871    );
12872
12873    editor.update_in(cx, |editor, window, cx| {
12874        editor.set_text(
12875            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12876            window,
12877            cx,
12878        )
12879    });
12880    // Ensure we don't lock if code action hangs.
12881    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12882        move |params, _| async move {
12883            assert_eq!(
12884                params.text_document.uri,
12885                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12886            );
12887            futures::future::pending::<()>().await;
12888            unreachable!()
12889        },
12890    );
12891    let format = editor
12892        .update_in(cx, |editor, window, cx| {
12893            editor.perform_code_action_kind(
12894                project,
12895                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12896                window,
12897                cx,
12898            )
12899        })
12900        .unwrap();
12901    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12902    cx.executor().start_waiting();
12903    format.await;
12904    assert_eq!(
12905        editor.update(cx, |editor, cx| editor.text(cx)),
12906        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12907    );
12908}
12909
12910#[gpui::test]
12911async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12912    init_test(cx, |_| {});
12913
12914    let mut cx = EditorLspTestContext::new_rust(
12915        lsp::ServerCapabilities {
12916            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12917            ..Default::default()
12918        },
12919        cx,
12920    )
12921    .await;
12922
12923    cx.set_state(indoc! {"
12924        one.twoˇ
12925    "});
12926
12927    // The format request takes a long time. When it completes, it inserts
12928    // a newline and an indent before the `.`
12929    cx.lsp
12930        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12931            let executor = cx.background_executor().clone();
12932            async move {
12933                executor.timer(Duration::from_millis(100)).await;
12934                Ok(Some(vec![lsp::TextEdit {
12935                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12936                    new_text: "\n    ".into(),
12937                }]))
12938            }
12939        });
12940
12941    // Submit a format request.
12942    let format_1 = cx
12943        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12944        .unwrap();
12945    cx.executor().run_until_parked();
12946
12947    // Submit a second format request.
12948    let format_2 = cx
12949        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12950        .unwrap();
12951    cx.executor().run_until_parked();
12952
12953    // Wait for both format requests to complete
12954    cx.executor().advance_clock(Duration::from_millis(200));
12955    cx.executor().start_waiting();
12956    format_1.await.unwrap();
12957    cx.executor().start_waiting();
12958    format_2.await.unwrap();
12959
12960    // The formatting edits only happens once.
12961    cx.assert_editor_state(indoc! {"
12962        one
12963            .twoˇ
12964    "});
12965}
12966
12967#[gpui::test]
12968async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12969    init_test(cx, |settings| {
12970        settings.defaults.formatter = Some(FormatterList::default())
12971    });
12972
12973    let mut cx = EditorLspTestContext::new_rust(
12974        lsp::ServerCapabilities {
12975            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12976            ..Default::default()
12977        },
12978        cx,
12979    )
12980    .await;
12981
12982    // Record which buffer changes have been sent to the language server
12983    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12984    cx.lsp
12985        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12986            let buffer_changes = buffer_changes.clone();
12987            move |params, _| {
12988                buffer_changes.lock().extend(
12989                    params
12990                        .content_changes
12991                        .into_iter()
12992                        .map(|e| (e.range.unwrap(), e.text)),
12993                );
12994            }
12995        });
12996    // Handle formatting requests to the language server.
12997    cx.lsp
12998        .set_request_handler::<lsp::request::Formatting, _, _>({
12999            let buffer_changes = buffer_changes.clone();
13000            move |_, _| {
13001                let buffer_changes = buffer_changes.clone();
13002                // Insert blank lines between each line of the buffer.
13003                async move {
13004                    // When formatting is requested, trailing whitespace has already been stripped,
13005                    // and the trailing newline has already been added.
13006                    assert_eq!(
13007                        &buffer_changes.lock()[1..],
13008                        &[
13009                            (
13010                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13011                                "".into()
13012                            ),
13013                            (
13014                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13015                                "".into()
13016                            ),
13017                            (
13018                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13019                                "\n".into()
13020                            ),
13021                        ]
13022                    );
13023
13024                    Ok(Some(vec![
13025                        lsp::TextEdit {
13026                            range: lsp::Range::new(
13027                                lsp::Position::new(1, 0),
13028                                lsp::Position::new(1, 0),
13029                            ),
13030                            new_text: "\n".into(),
13031                        },
13032                        lsp::TextEdit {
13033                            range: lsp::Range::new(
13034                                lsp::Position::new(2, 0),
13035                                lsp::Position::new(2, 0),
13036                            ),
13037                            new_text: "\n".into(),
13038                        },
13039                    ]))
13040                }
13041            }
13042        });
13043
13044    // Set up a buffer white some trailing whitespace and no trailing newline.
13045    cx.set_state(
13046        &[
13047            "one ",   //
13048            "twoˇ",   //
13049            "three ", //
13050            "four",   //
13051        ]
13052        .join("\n"),
13053    );
13054    cx.run_until_parked();
13055
13056    // Submit a format request.
13057    let format = cx
13058        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13059        .unwrap();
13060
13061    cx.run_until_parked();
13062    // After formatting the buffer, the trailing whitespace is stripped,
13063    // a newline is appended, and the edits provided by the language server
13064    // have been applied.
13065    format.await.unwrap();
13066
13067    cx.assert_editor_state(
13068        &[
13069            "one",   //
13070            "",      //
13071            "twoˇ",  //
13072            "",      //
13073            "three", //
13074            "four",  //
13075            "",      //
13076        ]
13077        .join("\n"),
13078    );
13079
13080    // Undoing the formatting undoes the trailing whitespace removal, the
13081    // trailing newline, and the LSP edits.
13082    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13083    cx.assert_editor_state(
13084        &[
13085            "one ",   //
13086            "twoˇ",   //
13087            "three ", //
13088            "four",   //
13089        ]
13090        .join("\n"),
13091    );
13092}
13093
13094#[gpui::test]
13095async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13096    cx: &mut TestAppContext,
13097) {
13098    init_test(cx, |_| {});
13099
13100    cx.update(|cx| {
13101        cx.update_global::<SettingsStore, _>(|settings, cx| {
13102            settings.update_user_settings(cx, |settings| {
13103                settings.editor.auto_signature_help = Some(true);
13104            });
13105        });
13106    });
13107
13108    let mut cx = EditorLspTestContext::new_rust(
13109        lsp::ServerCapabilities {
13110            signature_help_provider: Some(lsp::SignatureHelpOptions {
13111                ..Default::default()
13112            }),
13113            ..Default::default()
13114        },
13115        cx,
13116    )
13117    .await;
13118
13119    let language = Language::new(
13120        LanguageConfig {
13121            name: "Rust".into(),
13122            brackets: BracketPairConfig {
13123                pairs: vec![
13124                    BracketPair {
13125                        start: "{".to_string(),
13126                        end: "}".to_string(),
13127                        close: true,
13128                        surround: true,
13129                        newline: true,
13130                    },
13131                    BracketPair {
13132                        start: "(".to_string(),
13133                        end: ")".to_string(),
13134                        close: true,
13135                        surround: true,
13136                        newline: true,
13137                    },
13138                    BracketPair {
13139                        start: "/*".to_string(),
13140                        end: " */".to_string(),
13141                        close: true,
13142                        surround: true,
13143                        newline: true,
13144                    },
13145                    BracketPair {
13146                        start: "[".to_string(),
13147                        end: "]".to_string(),
13148                        close: false,
13149                        surround: false,
13150                        newline: true,
13151                    },
13152                    BracketPair {
13153                        start: "\"".to_string(),
13154                        end: "\"".to_string(),
13155                        close: true,
13156                        surround: true,
13157                        newline: false,
13158                    },
13159                    BracketPair {
13160                        start: "<".to_string(),
13161                        end: ">".to_string(),
13162                        close: false,
13163                        surround: true,
13164                        newline: true,
13165                    },
13166                ],
13167                ..Default::default()
13168            },
13169            autoclose_before: "})]".to_string(),
13170            ..Default::default()
13171        },
13172        Some(tree_sitter_rust::LANGUAGE.into()),
13173    );
13174    let language = Arc::new(language);
13175
13176    cx.language_registry().add(language.clone());
13177    cx.update_buffer(|buffer, cx| {
13178        buffer.set_language(Some(language), cx);
13179    });
13180
13181    cx.set_state(
13182        &r#"
13183            fn main() {
13184                sampleˇ
13185            }
13186        "#
13187        .unindent(),
13188    );
13189
13190    cx.update_editor(|editor, window, cx| {
13191        editor.handle_input("(", window, cx);
13192    });
13193    cx.assert_editor_state(
13194        &"
13195            fn main() {
13196                sample(ˇ)
13197            }
13198        "
13199        .unindent(),
13200    );
13201
13202    let mocked_response = lsp::SignatureHelp {
13203        signatures: vec![lsp::SignatureInformation {
13204            label: "fn sample(param1: u8, param2: u8)".to_string(),
13205            documentation: None,
13206            parameters: Some(vec![
13207                lsp::ParameterInformation {
13208                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13209                    documentation: None,
13210                },
13211                lsp::ParameterInformation {
13212                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13213                    documentation: None,
13214                },
13215            ]),
13216            active_parameter: None,
13217        }],
13218        active_signature: Some(0),
13219        active_parameter: Some(0),
13220    };
13221    handle_signature_help_request(&mut cx, mocked_response).await;
13222
13223    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13224        .await;
13225
13226    cx.editor(|editor, _, _| {
13227        let signature_help_state = editor.signature_help_state.popover().cloned();
13228        let signature = signature_help_state.unwrap();
13229        assert_eq!(
13230            signature.signatures[signature.current_signature].label,
13231            "fn sample(param1: u8, param2: u8)"
13232        );
13233    });
13234}
13235
13236#[gpui::test]
13237async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13238    init_test(cx, |_| {});
13239
13240    cx.update(|cx| {
13241        cx.update_global::<SettingsStore, _>(|settings, cx| {
13242            settings.update_user_settings(cx, |settings| {
13243                settings.editor.auto_signature_help = Some(false);
13244                settings.editor.show_signature_help_after_edits = Some(false);
13245            });
13246        });
13247    });
13248
13249    let mut cx = EditorLspTestContext::new_rust(
13250        lsp::ServerCapabilities {
13251            signature_help_provider: Some(lsp::SignatureHelpOptions {
13252                ..Default::default()
13253            }),
13254            ..Default::default()
13255        },
13256        cx,
13257    )
13258    .await;
13259
13260    let language = Language::new(
13261        LanguageConfig {
13262            name: "Rust".into(),
13263            brackets: BracketPairConfig {
13264                pairs: vec![
13265                    BracketPair {
13266                        start: "{".to_string(),
13267                        end: "}".to_string(),
13268                        close: true,
13269                        surround: true,
13270                        newline: true,
13271                    },
13272                    BracketPair {
13273                        start: "(".to_string(),
13274                        end: ")".to_string(),
13275                        close: true,
13276                        surround: true,
13277                        newline: true,
13278                    },
13279                    BracketPair {
13280                        start: "/*".to_string(),
13281                        end: " */".to_string(),
13282                        close: true,
13283                        surround: true,
13284                        newline: true,
13285                    },
13286                    BracketPair {
13287                        start: "[".to_string(),
13288                        end: "]".to_string(),
13289                        close: false,
13290                        surround: false,
13291                        newline: true,
13292                    },
13293                    BracketPair {
13294                        start: "\"".to_string(),
13295                        end: "\"".to_string(),
13296                        close: true,
13297                        surround: true,
13298                        newline: false,
13299                    },
13300                    BracketPair {
13301                        start: "<".to_string(),
13302                        end: ">".to_string(),
13303                        close: false,
13304                        surround: true,
13305                        newline: true,
13306                    },
13307                ],
13308                ..Default::default()
13309            },
13310            autoclose_before: "})]".to_string(),
13311            ..Default::default()
13312        },
13313        Some(tree_sitter_rust::LANGUAGE.into()),
13314    );
13315    let language = Arc::new(language);
13316
13317    cx.language_registry().add(language.clone());
13318    cx.update_buffer(|buffer, cx| {
13319        buffer.set_language(Some(language), cx);
13320    });
13321
13322    // Ensure that signature_help is not called when no signature help is enabled.
13323    cx.set_state(
13324        &r#"
13325            fn main() {
13326                sampleˇ
13327            }
13328        "#
13329        .unindent(),
13330    );
13331    cx.update_editor(|editor, window, cx| {
13332        editor.handle_input("(", window, cx);
13333    });
13334    cx.assert_editor_state(
13335        &"
13336            fn main() {
13337                sample(ˇ)
13338            }
13339        "
13340        .unindent(),
13341    );
13342    cx.editor(|editor, _, _| {
13343        assert!(editor.signature_help_state.task().is_none());
13344    });
13345
13346    let mocked_response = lsp::SignatureHelp {
13347        signatures: vec![lsp::SignatureInformation {
13348            label: "fn sample(param1: u8, param2: u8)".to_string(),
13349            documentation: None,
13350            parameters: Some(vec![
13351                lsp::ParameterInformation {
13352                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13353                    documentation: None,
13354                },
13355                lsp::ParameterInformation {
13356                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13357                    documentation: None,
13358                },
13359            ]),
13360            active_parameter: None,
13361        }],
13362        active_signature: Some(0),
13363        active_parameter: Some(0),
13364    };
13365
13366    // Ensure that signature_help is called when enabled afte edits
13367    cx.update(|_, cx| {
13368        cx.update_global::<SettingsStore, _>(|settings, cx| {
13369            settings.update_user_settings(cx, |settings| {
13370                settings.editor.auto_signature_help = Some(false);
13371                settings.editor.show_signature_help_after_edits = Some(true);
13372            });
13373        });
13374    });
13375    cx.set_state(
13376        &r#"
13377            fn main() {
13378                sampleˇ
13379            }
13380        "#
13381        .unindent(),
13382    );
13383    cx.update_editor(|editor, window, cx| {
13384        editor.handle_input("(", window, cx);
13385    });
13386    cx.assert_editor_state(
13387        &"
13388            fn main() {
13389                sample(ˇ)
13390            }
13391        "
13392        .unindent(),
13393    );
13394    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13395    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13396        .await;
13397    cx.update_editor(|editor, _, _| {
13398        let signature_help_state = editor.signature_help_state.popover().cloned();
13399        assert!(signature_help_state.is_some());
13400        let signature = signature_help_state.unwrap();
13401        assert_eq!(
13402            signature.signatures[signature.current_signature].label,
13403            "fn sample(param1: u8, param2: u8)"
13404        );
13405        editor.signature_help_state = SignatureHelpState::default();
13406    });
13407
13408    // Ensure that signature_help is called when auto signature help override is enabled
13409    cx.update(|_, cx| {
13410        cx.update_global::<SettingsStore, _>(|settings, cx| {
13411            settings.update_user_settings(cx, |settings| {
13412                settings.editor.auto_signature_help = Some(true);
13413                settings.editor.show_signature_help_after_edits = Some(false);
13414            });
13415        });
13416    });
13417    cx.set_state(
13418        &r#"
13419            fn main() {
13420                sampleˇ
13421            }
13422        "#
13423        .unindent(),
13424    );
13425    cx.update_editor(|editor, window, cx| {
13426        editor.handle_input("(", window, cx);
13427    });
13428    cx.assert_editor_state(
13429        &"
13430            fn main() {
13431                sample(ˇ)
13432            }
13433        "
13434        .unindent(),
13435    );
13436    handle_signature_help_request(&mut cx, mocked_response).await;
13437    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13438        .await;
13439    cx.editor(|editor, _, _| {
13440        let signature_help_state = editor.signature_help_state.popover().cloned();
13441        assert!(signature_help_state.is_some());
13442        let signature = signature_help_state.unwrap();
13443        assert_eq!(
13444            signature.signatures[signature.current_signature].label,
13445            "fn sample(param1: u8, param2: u8)"
13446        );
13447    });
13448}
13449
13450#[gpui::test]
13451async fn test_signature_help(cx: &mut TestAppContext) {
13452    init_test(cx, |_| {});
13453    cx.update(|cx| {
13454        cx.update_global::<SettingsStore, _>(|settings, cx| {
13455            settings.update_user_settings(cx, |settings| {
13456                settings.editor.auto_signature_help = Some(true);
13457            });
13458        });
13459    });
13460
13461    let mut cx = EditorLspTestContext::new_rust(
13462        lsp::ServerCapabilities {
13463            signature_help_provider: Some(lsp::SignatureHelpOptions {
13464                ..Default::default()
13465            }),
13466            ..Default::default()
13467        },
13468        cx,
13469    )
13470    .await;
13471
13472    // A test that directly calls `show_signature_help`
13473    cx.update_editor(|editor, window, cx| {
13474        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13475    });
13476
13477    let mocked_response = lsp::SignatureHelp {
13478        signatures: vec![lsp::SignatureInformation {
13479            label: "fn sample(param1: u8, param2: u8)".to_string(),
13480            documentation: None,
13481            parameters: Some(vec![
13482                lsp::ParameterInformation {
13483                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13484                    documentation: None,
13485                },
13486                lsp::ParameterInformation {
13487                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13488                    documentation: None,
13489                },
13490            ]),
13491            active_parameter: None,
13492        }],
13493        active_signature: Some(0),
13494        active_parameter: Some(0),
13495    };
13496    handle_signature_help_request(&mut cx, mocked_response).await;
13497
13498    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13499        .await;
13500
13501    cx.editor(|editor, _, _| {
13502        let signature_help_state = editor.signature_help_state.popover().cloned();
13503        assert!(signature_help_state.is_some());
13504        let signature = signature_help_state.unwrap();
13505        assert_eq!(
13506            signature.signatures[signature.current_signature].label,
13507            "fn sample(param1: u8, param2: u8)"
13508        );
13509    });
13510
13511    // When exiting outside from inside the brackets, `signature_help` is closed.
13512    cx.set_state(indoc! {"
13513        fn main() {
13514            sample(ˇ);
13515        }
13516
13517        fn sample(param1: u8, param2: u8) {}
13518    "});
13519
13520    cx.update_editor(|editor, window, cx| {
13521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13522            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13523        });
13524    });
13525
13526    let mocked_response = lsp::SignatureHelp {
13527        signatures: Vec::new(),
13528        active_signature: None,
13529        active_parameter: None,
13530    };
13531    handle_signature_help_request(&mut cx, mocked_response).await;
13532
13533    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13534        .await;
13535
13536    cx.editor(|editor, _, _| {
13537        assert!(!editor.signature_help_state.is_shown());
13538    });
13539
13540    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13541    cx.set_state(indoc! {"
13542        fn main() {
13543            sample(ˇ);
13544        }
13545
13546        fn sample(param1: u8, param2: u8) {}
13547    "});
13548
13549    let mocked_response = lsp::SignatureHelp {
13550        signatures: vec![lsp::SignatureInformation {
13551            label: "fn sample(param1: u8, param2: u8)".to_string(),
13552            documentation: None,
13553            parameters: Some(vec![
13554                lsp::ParameterInformation {
13555                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13556                    documentation: None,
13557                },
13558                lsp::ParameterInformation {
13559                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13560                    documentation: None,
13561                },
13562            ]),
13563            active_parameter: None,
13564        }],
13565        active_signature: Some(0),
13566        active_parameter: Some(0),
13567    };
13568    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13569    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13570        .await;
13571    cx.editor(|editor, _, _| {
13572        assert!(editor.signature_help_state.is_shown());
13573    });
13574
13575    // Restore the popover with more parameter input
13576    cx.set_state(indoc! {"
13577        fn main() {
13578            sample(param1, param2ˇ);
13579        }
13580
13581        fn sample(param1: u8, param2: u8) {}
13582    "});
13583
13584    let mocked_response = lsp::SignatureHelp {
13585        signatures: vec![lsp::SignatureInformation {
13586            label: "fn sample(param1: u8, param2: u8)".to_string(),
13587            documentation: None,
13588            parameters: Some(vec![
13589                lsp::ParameterInformation {
13590                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13591                    documentation: None,
13592                },
13593                lsp::ParameterInformation {
13594                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13595                    documentation: None,
13596                },
13597            ]),
13598            active_parameter: None,
13599        }],
13600        active_signature: Some(0),
13601        active_parameter: Some(1),
13602    };
13603    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13604    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13605        .await;
13606
13607    // When selecting a range, the popover is gone.
13608    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13609    cx.update_editor(|editor, window, cx| {
13610        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13611            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13612        })
13613    });
13614    cx.assert_editor_state(indoc! {"
13615        fn main() {
13616            sample(param1, «ˇparam2»);
13617        }
13618
13619        fn sample(param1: u8, param2: u8) {}
13620    "});
13621    cx.editor(|editor, _, _| {
13622        assert!(!editor.signature_help_state.is_shown());
13623    });
13624
13625    // When unselecting again, the popover is back if within the brackets.
13626    cx.update_editor(|editor, window, cx| {
13627        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13628            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13629        })
13630    });
13631    cx.assert_editor_state(indoc! {"
13632        fn main() {
13633            sample(param1, ˇparam2);
13634        }
13635
13636        fn sample(param1: u8, param2: u8) {}
13637    "});
13638    handle_signature_help_request(&mut cx, mocked_response).await;
13639    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13640        .await;
13641    cx.editor(|editor, _, _| {
13642        assert!(editor.signature_help_state.is_shown());
13643    });
13644
13645    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13646    cx.update_editor(|editor, window, cx| {
13647        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13648            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13649            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13650        })
13651    });
13652    cx.assert_editor_state(indoc! {"
13653        fn main() {
13654            sample(param1, ˇparam2);
13655        }
13656
13657        fn sample(param1: u8, param2: u8) {}
13658    "});
13659
13660    let mocked_response = lsp::SignatureHelp {
13661        signatures: vec![lsp::SignatureInformation {
13662            label: "fn sample(param1: u8, param2: u8)".to_string(),
13663            documentation: None,
13664            parameters: Some(vec![
13665                lsp::ParameterInformation {
13666                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13667                    documentation: None,
13668                },
13669                lsp::ParameterInformation {
13670                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13671                    documentation: None,
13672                },
13673            ]),
13674            active_parameter: None,
13675        }],
13676        active_signature: Some(0),
13677        active_parameter: Some(1),
13678    };
13679    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13680    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13681        .await;
13682    cx.update_editor(|editor, _, cx| {
13683        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13684    });
13685    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13686        .await;
13687    cx.update_editor(|editor, window, cx| {
13688        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13689            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13690        })
13691    });
13692    cx.assert_editor_state(indoc! {"
13693        fn main() {
13694            sample(param1, «ˇparam2»);
13695        }
13696
13697        fn sample(param1: u8, param2: u8) {}
13698    "});
13699    cx.update_editor(|editor, window, cx| {
13700        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13701            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13702        })
13703    });
13704    cx.assert_editor_state(indoc! {"
13705        fn main() {
13706            sample(param1, ˇparam2);
13707        }
13708
13709        fn sample(param1: u8, param2: u8) {}
13710    "});
13711    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13712        .await;
13713}
13714
13715#[gpui::test]
13716async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13717    init_test(cx, |_| {});
13718
13719    let mut cx = EditorLspTestContext::new_rust(
13720        lsp::ServerCapabilities {
13721            signature_help_provider: Some(lsp::SignatureHelpOptions {
13722                ..Default::default()
13723            }),
13724            ..Default::default()
13725        },
13726        cx,
13727    )
13728    .await;
13729
13730    cx.set_state(indoc! {"
13731        fn main() {
13732            overloadedˇ
13733        }
13734    "});
13735
13736    cx.update_editor(|editor, window, cx| {
13737        editor.handle_input("(", window, cx);
13738        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13739    });
13740
13741    // Mock response with 3 signatures
13742    let mocked_response = lsp::SignatureHelp {
13743        signatures: vec![
13744            lsp::SignatureInformation {
13745                label: "fn overloaded(x: i32)".to_string(),
13746                documentation: None,
13747                parameters: Some(vec![lsp::ParameterInformation {
13748                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13749                    documentation: None,
13750                }]),
13751                active_parameter: None,
13752            },
13753            lsp::SignatureInformation {
13754                label: "fn overloaded(x: i32, y: i32)".to_string(),
13755                documentation: None,
13756                parameters: Some(vec![
13757                    lsp::ParameterInformation {
13758                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13759                        documentation: None,
13760                    },
13761                    lsp::ParameterInformation {
13762                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13763                        documentation: None,
13764                    },
13765                ]),
13766                active_parameter: None,
13767            },
13768            lsp::SignatureInformation {
13769                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13770                documentation: None,
13771                parameters: Some(vec![
13772                    lsp::ParameterInformation {
13773                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13774                        documentation: None,
13775                    },
13776                    lsp::ParameterInformation {
13777                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13778                        documentation: None,
13779                    },
13780                    lsp::ParameterInformation {
13781                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13782                        documentation: None,
13783                    },
13784                ]),
13785                active_parameter: None,
13786            },
13787        ],
13788        active_signature: Some(1),
13789        active_parameter: Some(0),
13790    };
13791    handle_signature_help_request(&mut cx, mocked_response).await;
13792
13793    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13794        .await;
13795
13796    // Verify we have multiple signatures and the right one is selected
13797    cx.editor(|editor, _, _| {
13798        let popover = editor.signature_help_state.popover().cloned().unwrap();
13799        assert_eq!(popover.signatures.len(), 3);
13800        // active_signature was 1, so that should be the current
13801        assert_eq!(popover.current_signature, 1);
13802        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13803        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13804        assert_eq!(
13805            popover.signatures[2].label,
13806            "fn overloaded(x: i32, y: i32, z: i32)"
13807        );
13808    });
13809
13810    // Test navigation functionality
13811    cx.update_editor(|editor, window, cx| {
13812        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13813    });
13814
13815    cx.editor(|editor, _, _| {
13816        let popover = editor.signature_help_state.popover().cloned().unwrap();
13817        assert_eq!(popover.current_signature, 2);
13818    });
13819
13820    // Test wrap around
13821    cx.update_editor(|editor, window, cx| {
13822        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13823    });
13824
13825    cx.editor(|editor, _, _| {
13826        let popover = editor.signature_help_state.popover().cloned().unwrap();
13827        assert_eq!(popover.current_signature, 0);
13828    });
13829
13830    // Test previous navigation
13831    cx.update_editor(|editor, window, cx| {
13832        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13833    });
13834
13835    cx.editor(|editor, _, _| {
13836        let popover = editor.signature_help_state.popover().cloned().unwrap();
13837        assert_eq!(popover.current_signature, 2);
13838    });
13839}
13840
13841#[gpui::test]
13842async fn test_completion_mode(cx: &mut TestAppContext) {
13843    init_test(cx, |_| {});
13844    let mut cx = EditorLspTestContext::new_rust(
13845        lsp::ServerCapabilities {
13846            completion_provider: Some(lsp::CompletionOptions {
13847                resolve_provider: Some(true),
13848                ..Default::default()
13849            }),
13850            ..Default::default()
13851        },
13852        cx,
13853    )
13854    .await;
13855
13856    struct Run {
13857        run_description: &'static str,
13858        initial_state: String,
13859        buffer_marked_text: String,
13860        completion_label: &'static str,
13861        completion_text: &'static str,
13862        expected_with_insert_mode: String,
13863        expected_with_replace_mode: String,
13864        expected_with_replace_subsequence_mode: String,
13865        expected_with_replace_suffix_mode: String,
13866    }
13867
13868    let runs = [
13869        Run {
13870            run_description: "Start of word matches completion text",
13871            initial_state: "before ediˇ after".into(),
13872            buffer_marked_text: "before <edi|> after".into(),
13873            completion_label: "editor",
13874            completion_text: "editor",
13875            expected_with_insert_mode: "before editorˇ after".into(),
13876            expected_with_replace_mode: "before editorˇ after".into(),
13877            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13878            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13879        },
13880        Run {
13881            run_description: "Accept same text at the middle of the word",
13882            initial_state: "before ediˇtor after".into(),
13883            buffer_marked_text: "before <edi|tor> after".into(),
13884            completion_label: "editor",
13885            completion_text: "editor",
13886            expected_with_insert_mode: "before editorˇtor after".into(),
13887            expected_with_replace_mode: "before editorˇ after".into(),
13888            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13889            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13890        },
13891        Run {
13892            run_description: "End of word matches completion text -- cursor at end",
13893            initial_state: "before torˇ after".into(),
13894            buffer_marked_text: "before <tor|> after".into(),
13895            completion_label: "editor",
13896            completion_text: "editor",
13897            expected_with_insert_mode: "before editorˇ after".into(),
13898            expected_with_replace_mode: "before editorˇ after".into(),
13899            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13900            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13901        },
13902        Run {
13903            run_description: "End of word matches completion text -- cursor at start",
13904            initial_state: "before ˇtor after".into(),
13905            buffer_marked_text: "before <|tor> after".into(),
13906            completion_label: "editor",
13907            completion_text: "editor",
13908            expected_with_insert_mode: "before editorˇtor after".into(),
13909            expected_with_replace_mode: "before editorˇ after".into(),
13910            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13911            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13912        },
13913        Run {
13914            run_description: "Prepend text containing whitespace",
13915            initial_state: "pˇfield: bool".into(),
13916            buffer_marked_text: "<p|field>: bool".into(),
13917            completion_label: "pub ",
13918            completion_text: "pub ",
13919            expected_with_insert_mode: "pub ˇfield: bool".into(),
13920            expected_with_replace_mode: "pub ˇ: bool".into(),
13921            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13922            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13923        },
13924        Run {
13925            run_description: "Add element to start of list",
13926            initial_state: "[element_ˇelement_2]".into(),
13927            buffer_marked_text: "[<element_|element_2>]".into(),
13928            completion_label: "element_1",
13929            completion_text: "element_1",
13930            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13931            expected_with_replace_mode: "[element_1ˇ]".into(),
13932            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13933            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13934        },
13935        Run {
13936            run_description: "Add element to start of list -- first and second elements are equal",
13937            initial_state: "[elˇelement]".into(),
13938            buffer_marked_text: "[<el|element>]".into(),
13939            completion_label: "element",
13940            completion_text: "element",
13941            expected_with_insert_mode: "[elementˇelement]".into(),
13942            expected_with_replace_mode: "[elementˇ]".into(),
13943            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13944            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13945        },
13946        Run {
13947            run_description: "Ends with matching suffix",
13948            initial_state: "SubˇError".into(),
13949            buffer_marked_text: "<Sub|Error>".into(),
13950            completion_label: "SubscriptionError",
13951            completion_text: "SubscriptionError",
13952            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13953            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13954            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13955            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13956        },
13957        Run {
13958            run_description: "Suffix is a subsequence -- contiguous",
13959            initial_state: "SubˇErr".into(),
13960            buffer_marked_text: "<Sub|Err>".into(),
13961            completion_label: "SubscriptionError",
13962            completion_text: "SubscriptionError",
13963            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13964            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13965            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13966            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13967        },
13968        Run {
13969            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13970            initial_state: "Suˇscrirr".into(),
13971            buffer_marked_text: "<Su|scrirr>".into(),
13972            completion_label: "SubscriptionError",
13973            completion_text: "SubscriptionError",
13974            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13975            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13976            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13977            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13978        },
13979        Run {
13980            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13981            initial_state: "foo(indˇix)".into(),
13982            buffer_marked_text: "foo(<ind|ix>)".into(),
13983            completion_label: "node_index",
13984            completion_text: "node_index",
13985            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13986            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13987            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13988            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13989        },
13990        Run {
13991            run_description: "Replace range ends before cursor - should extend to cursor",
13992            initial_state: "before editˇo after".into(),
13993            buffer_marked_text: "before <{ed}>it|o after".into(),
13994            completion_label: "editor",
13995            completion_text: "editor",
13996            expected_with_insert_mode: "before editorˇo after".into(),
13997            expected_with_replace_mode: "before editorˇo after".into(),
13998            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13999            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14000        },
14001        Run {
14002            run_description: "Uses label for suffix matching",
14003            initial_state: "before ediˇtor after".into(),
14004            buffer_marked_text: "before <edi|tor> after".into(),
14005            completion_label: "editor",
14006            completion_text: "editor()",
14007            expected_with_insert_mode: "before editor()ˇtor after".into(),
14008            expected_with_replace_mode: "before editor()ˇ after".into(),
14009            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14010            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14011        },
14012        Run {
14013            run_description: "Case insensitive subsequence and suffix matching",
14014            initial_state: "before EDiˇtoR after".into(),
14015            buffer_marked_text: "before <EDi|toR> after".into(),
14016            completion_label: "editor",
14017            completion_text: "editor",
14018            expected_with_insert_mode: "before editorˇtoR after".into(),
14019            expected_with_replace_mode: "before editorˇ after".into(),
14020            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14021            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14022        },
14023    ];
14024
14025    for run in runs {
14026        let run_variations = [
14027            (LspInsertMode::Insert, run.expected_with_insert_mode),
14028            (LspInsertMode::Replace, run.expected_with_replace_mode),
14029            (
14030                LspInsertMode::ReplaceSubsequence,
14031                run.expected_with_replace_subsequence_mode,
14032            ),
14033            (
14034                LspInsertMode::ReplaceSuffix,
14035                run.expected_with_replace_suffix_mode,
14036            ),
14037        ];
14038
14039        for (lsp_insert_mode, expected_text) in run_variations {
14040            eprintln!(
14041                "run = {:?}, mode = {lsp_insert_mode:.?}",
14042                run.run_description,
14043            );
14044
14045            update_test_language_settings(&mut cx, |settings| {
14046                settings.defaults.completions = Some(CompletionSettingsContent {
14047                    lsp_insert_mode: Some(lsp_insert_mode),
14048                    words: Some(WordsCompletionMode::Disabled),
14049                    words_min_length: Some(0),
14050                    ..Default::default()
14051                });
14052            });
14053
14054            cx.set_state(&run.initial_state);
14055            cx.update_editor(|editor, window, cx| {
14056                editor.show_completions(&ShowCompletions, window, cx);
14057            });
14058
14059            let counter = Arc::new(AtomicUsize::new(0));
14060            handle_completion_request_with_insert_and_replace(
14061                &mut cx,
14062                &run.buffer_marked_text,
14063                vec![(run.completion_label, run.completion_text)],
14064                counter.clone(),
14065            )
14066            .await;
14067            cx.condition(|editor, _| editor.context_menu_visible())
14068                .await;
14069            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14070
14071            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14072                editor
14073                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14074                    .unwrap()
14075            });
14076            cx.assert_editor_state(&expected_text);
14077            handle_resolve_completion_request(&mut cx, None).await;
14078            apply_additional_edits.await.unwrap();
14079        }
14080    }
14081}
14082
14083#[gpui::test]
14084async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14085    init_test(cx, |_| {});
14086    let mut cx = EditorLspTestContext::new_rust(
14087        lsp::ServerCapabilities {
14088            completion_provider: Some(lsp::CompletionOptions {
14089                resolve_provider: Some(true),
14090                ..Default::default()
14091            }),
14092            ..Default::default()
14093        },
14094        cx,
14095    )
14096    .await;
14097
14098    let initial_state = "SubˇError";
14099    let buffer_marked_text = "<Sub|Error>";
14100    let completion_text = "SubscriptionError";
14101    let expected_with_insert_mode = "SubscriptionErrorˇError";
14102    let expected_with_replace_mode = "SubscriptionErrorˇ";
14103
14104    update_test_language_settings(&mut cx, |settings| {
14105        settings.defaults.completions = Some(CompletionSettingsContent {
14106            words: Some(WordsCompletionMode::Disabled),
14107            words_min_length: Some(0),
14108            // set the opposite here to ensure that the action is overriding the default behavior
14109            lsp_insert_mode: Some(LspInsertMode::Insert),
14110            ..Default::default()
14111        });
14112    });
14113
14114    cx.set_state(initial_state);
14115    cx.update_editor(|editor, window, cx| {
14116        editor.show_completions(&ShowCompletions, window, cx);
14117    });
14118
14119    let counter = Arc::new(AtomicUsize::new(0));
14120    handle_completion_request_with_insert_and_replace(
14121        &mut cx,
14122        buffer_marked_text,
14123        vec![(completion_text, completion_text)],
14124        counter.clone(),
14125    )
14126    .await;
14127    cx.condition(|editor, _| editor.context_menu_visible())
14128        .await;
14129    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14130
14131    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14132        editor
14133            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14134            .unwrap()
14135    });
14136    cx.assert_editor_state(expected_with_replace_mode);
14137    handle_resolve_completion_request(&mut cx, None).await;
14138    apply_additional_edits.await.unwrap();
14139
14140    update_test_language_settings(&mut cx, |settings| {
14141        settings.defaults.completions = Some(CompletionSettingsContent {
14142            words: Some(WordsCompletionMode::Disabled),
14143            words_min_length: Some(0),
14144            // set the opposite here to ensure that the action is overriding the default behavior
14145            lsp_insert_mode: Some(LspInsertMode::Replace),
14146            ..Default::default()
14147        });
14148    });
14149
14150    cx.set_state(initial_state);
14151    cx.update_editor(|editor, window, cx| {
14152        editor.show_completions(&ShowCompletions, window, cx);
14153    });
14154    handle_completion_request_with_insert_and_replace(
14155        &mut cx,
14156        buffer_marked_text,
14157        vec![(completion_text, completion_text)],
14158        counter.clone(),
14159    )
14160    .await;
14161    cx.condition(|editor, _| editor.context_menu_visible())
14162        .await;
14163    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14164
14165    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14166        editor
14167            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14168            .unwrap()
14169    });
14170    cx.assert_editor_state(expected_with_insert_mode);
14171    handle_resolve_completion_request(&mut cx, None).await;
14172    apply_additional_edits.await.unwrap();
14173}
14174
14175#[gpui::test]
14176async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14177    init_test(cx, |_| {});
14178    let mut cx = EditorLspTestContext::new_rust(
14179        lsp::ServerCapabilities {
14180            completion_provider: Some(lsp::CompletionOptions {
14181                resolve_provider: Some(true),
14182                ..Default::default()
14183            }),
14184            ..Default::default()
14185        },
14186        cx,
14187    )
14188    .await;
14189
14190    // scenario: surrounding text matches completion text
14191    let completion_text = "to_offset";
14192    let initial_state = indoc! {"
14193        1. buf.to_offˇsuffix
14194        2. buf.to_offˇsuf
14195        3. buf.to_offˇfix
14196        4. buf.to_offˇ
14197        5. into_offˇensive
14198        6. ˇsuffix
14199        7. let ˇ //
14200        8. aaˇzz
14201        9. buf.to_off«zzzzzˇ»suffix
14202        10. buf.«ˇzzzzz»suffix
14203        11. to_off«ˇzzzzz»
14204
14205        buf.to_offˇsuffix  // newest cursor
14206    "};
14207    let completion_marked_buffer = indoc! {"
14208        1. buf.to_offsuffix
14209        2. buf.to_offsuf
14210        3. buf.to_offfix
14211        4. buf.to_off
14212        5. into_offensive
14213        6. suffix
14214        7. let  //
14215        8. aazz
14216        9. buf.to_offzzzzzsuffix
14217        10. buf.zzzzzsuffix
14218        11. to_offzzzzz
14219
14220        buf.<to_off|suffix>  // newest cursor
14221    "};
14222    let expected = indoc! {"
14223        1. buf.to_offsetˇ
14224        2. buf.to_offsetˇsuf
14225        3. buf.to_offsetˇfix
14226        4. buf.to_offsetˇ
14227        5. into_offsetˇensive
14228        6. to_offsetˇsuffix
14229        7. let to_offsetˇ //
14230        8. aato_offsetˇzz
14231        9. buf.to_offsetˇ
14232        10. buf.to_offsetˇsuffix
14233        11. to_offsetˇ
14234
14235        buf.to_offsetˇ  // newest cursor
14236    "};
14237    cx.set_state(initial_state);
14238    cx.update_editor(|editor, window, cx| {
14239        editor.show_completions(&ShowCompletions, window, cx);
14240    });
14241    handle_completion_request_with_insert_and_replace(
14242        &mut cx,
14243        completion_marked_buffer,
14244        vec![(completion_text, completion_text)],
14245        Arc::new(AtomicUsize::new(0)),
14246    )
14247    .await;
14248    cx.condition(|editor, _| editor.context_menu_visible())
14249        .await;
14250    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14251        editor
14252            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14253            .unwrap()
14254    });
14255    cx.assert_editor_state(expected);
14256    handle_resolve_completion_request(&mut cx, None).await;
14257    apply_additional_edits.await.unwrap();
14258
14259    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14260    let completion_text = "foo_and_bar";
14261    let initial_state = indoc! {"
14262        1. ooanbˇ
14263        2. zooanbˇ
14264        3. ooanbˇz
14265        4. zooanbˇz
14266        5. ooanˇ
14267        6. oanbˇ
14268
14269        ooanbˇ
14270    "};
14271    let completion_marked_buffer = indoc! {"
14272        1. ooanb
14273        2. zooanb
14274        3. ooanbz
14275        4. zooanbz
14276        5. ooan
14277        6. oanb
14278
14279        <ooanb|>
14280    "};
14281    let expected = indoc! {"
14282        1. foo_and_barˇ
14283        2. zfoo_and_barˇ
14284        3. foo_and_barˇz
14285        4. zfoo_and_barˇz
14286        5. ooanfoo_and_barˇ
14287        6. oanbfoo_and_barˇ
14288
14289        foo_and_barˇ
14290    "};
14291    cx.set_state(initial_state);
14292    cx.update_editor(|editor, window, cx| {
14293        editor.show_completions(&ShowCompletions, window, cx);
14294    });
14295    handle_completion_request_with_insert_and_replace(
14296        &mut cx,
14297        completion_marked_buffer,
14298        vec![(completion_text, completion_text)],
14299        Arc::new(AtomicUsize::new(0)),
14300    )
14301    .await;
14302    cx.condition(|editor, _| editor.context_menu_visible())
14303        .await;
14304    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14305        editor
14306            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14307            .unwrap()
14308    });
14309    cx.assert_editor_state(expected);
14310    handle_resolve_completion_request(&mut cx, None).await;
14311    apply_additional_edits.await.unwrap();
14312
14313    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14314    // (expects the same as if it was inserted at the end)
14315    let completion_text = "foo_and_bar";
14316    let initial_state = indoc! {"
14317        1. ooˇanb
14318        2. zooˇanb
14319        3. ooˇanbz
14320        4. zooˇanbz
14321
14322        ooˇanb
14323    "};
14324    let completion_marked_buffer = indoc! {"
14325        1. ooanb
14326        2. zooanb
14327        3. ooanbz
14328        4. zooanbz
14329
14330        <oo|anb>
14331    "};
14332    let expected = indoc! {"
14333        1. foo_and_barˇ
14334        2. zfoo_and_barˇ
14335        3. foo_and_barˇz
14336        4. zfoo_and_barˇz
14337
14338        foo_and_barˇ
14339    "};
14340    cx.set_state(initial_state);
14341    cx.update_editor(|editor, window, cx| {
14342        editor.show_completions(&ShowCompletions, window, cx);
14343    });
14344    handle_completion_request_with_insert_and_replace(
14345        &mut cx,
14346        completion_marked_buffer,
14347        vec![(completion_text, completion_text)],
14348        Arc::new(AtomicUsize::new(0)),
14349    )
14350    .await;
14351    cx.condition(|editor, _| editor.context_menu_visible())
14352        .await;
14353    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14354        editor
14355            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14356            .unwrap()
14357    });
14358    cx.assert_editor_state(expected);
14359    handle_resolve_completion_request(&mut cx, None).await;
14360    apply_additional_edits.await.unwrap();
14361}
14362
14363// This used to crash
14364#[gpui::test]
14365async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14366    init_test(cx, |_| {});
14367
14368    let buffer_text = indoc! {"
14369        fn main() {
14370            10.satu;
14371
14372            //
14373            // separate cursors so they open in different excerpts (manually reproducible)
14374            //
14375
14376            10.satu20;
14377        }
14378    "};
14379    let multibuffer_text_with_selections = indoc! {"
14380        fn main() {
14381            10.satuˇ;
14382
14383            //
14384
14385            //
14386
14387            10.satuˇ20;
14388        }
14389    "};
14390    let expected_multibuffer = indoc! {"
14391        fn main() {
14392            10.saturating_sub()ˇ;
14393
14394            //
14395
14396            //
14397
14398            10.saturating_sub()ˇ;
14399        }
14400    "};
14401
14402    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14403    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14404
14405    let fs = FakeFs::new(cx.executor());
14406    fs.insert_tree(
14407        path!("/a"),
14408        json!({
14409            "main.rs": buffer_text,
14410        }),
14411    )
14412    .await;
14413
14414    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14415    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14416    language_registry.add(rust_lang());
14417    let mut fake_servers = language_registry.register_fake_lsp(
14418        "Rust",
14419        FakeLspAdapter {
14420            capabilities: lsp::ServerCapabilities {
14421                completion_provider: Some(lsp::CompletionOptions {
14422                    resolve_provider: None,
14423                    ..lsp::CompletionOptions::default()
14424                }),
14425                ..lsp::ServerCapabilities::default()
14426            },
14427            ..FakeLspAdapter::default()
14428        },
14429    );
14430    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14431    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14432    let buffer = project
14433        .update(cx, |project, cx| {
14434            project.open_local_buffer(path!("/a/main.rs"), cx)
14435        })
14436        .await
14437        .unwrap();
14438
14439    let multi_buffer = cx.new(|cx| {
14440        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14441        multi_buffer.push_excerpts(
14442            buffer.clone(),
14443            [ExcerptRange::new(0..first_excerpt_end)],
14444            cx,
14445        );
14446        multi_buffer.push_excerpts(
14447            buffer.clone(),
14448            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14449            cx,
14450        );
14451        multi_buffer
14452    });
14453
14454    let editor = workspace
14455        .update(cx, |_, window, cx| {
14456            cx.new(|cx| {
14457                Editor::new(
14458                    EditorMode::Full {
14459                        scale_ui_elements_with_buffer_font_size: false,
14460                        show_active_line_background: false,
14461                        sizing_behavior: SizingBehavior::Default,
14462                    },
14463                    multi_buffer.clone(),
14464                    Some(project.clone()),
14465                    window,
14466                    cx,
14467                )
14468            })
14469        })
14470        .unwrap();
14471
14472    let pane = workspace
14473        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14474        .unwrap();
14475    pane.update_in(cx, |pane, window, cx| {
14476        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14477    });
14478
14479    let fake_server = fake_servers.next().await.unwrap();
14480
14481    editor.update_in(cx, |editor, window, cx| {
14482        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14483            s.select_ranges([
14484                Point::new(1, 11)..Point::new(1, 11),
14485                Point::new(7, 11)..Point::new(7, 11),
14486            ])
14487        });
14488
14489        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14490    });
14491
14492    editor.update_in(cx, |editor, window, cx| {
14493        editor.show_completions(&ShowCompletions, window, cx);
14494    });
14495
14496    fake_server
14497        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14498            let completion_item = lsp::CompletionItem {
14499                label: "saturating_sub()".into(),
14500                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14501                    lsp::InsertReplaceEdit {
14502                        new_text: "saturating_sub()".to_owned(),
14503                        insert: lsp::Range::new(
14504                            lsp::Position::new(7, 7),
14505                            lsp::Position::new(7, 11),
14506                        ),
14507                        replace: lsp::Range::new(
14508                            lsp::Position::new(7, 7),
14509                            lsp::Position::new(7, 13),
14510                        ),
14511                    },
14512                )),
14513                ..lsp::CompletionItem::default()
14514            };
14515
14516            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14517        })
14518        .next()
14519        .await
14520        .unwrap();
14521
14522    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14523        .await;
14524
14525    editor
14526        .update_in(cx, |editor, window, cx| {
14527            editor
14528                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14529                .unwrap()
14530        })
14531        .await
14532        .unwrap();
14533
14534    editor.update(cx, |editor, cx| {
14535        assert_text_with_selections(editor, expected_multibuffer, cx);
14536    })
14537}
14538
14539#[gpui::test]
14540async fn test_completion(cx: &mut TestAppContext) {
14541    init_test(cx, |_| {});
14542
14543    let mut cx = EditorLspTestContext::new_rust(
14544        lsp::ServerCapabilities {
14545            completion_provider: Some(lsp::CompletionOptions {
14546                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14547                resolve_provider: Some(true),
14548                ..Default::default()
14549            }),
14550            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14551            ..Default::default()
14552        },
14553        cx,
14554    )
14555    .await;
14556    let counter = Arc::new(AtomicUsize::new(0));
14557
14558    cx.set_state(indoc! {"
14559        oneˇ
14560        two
14561        three
14562    "});
14563    cx.simulate_keystroke(".");
14564    handle_completion_request(
14565        indoc! {"
14566            one.|<>
14567            two
14568            three
14569        "},
14570        vec!["first_completion", "second_completion"],
14571        true,
14572        counter.clone(),
14573        &mut cx,
14574    )
14575    .await;
14576    cx.condition(|editor, _| editor.context_menu_visible())
14577        .await;
14578    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14579
14580    let _handler = handle_signature_help_request(
14581        &mut cx,
14582        lsp::SignatureHelp {
14583            signatures: vec![lsp::SignatureInformation {
14584                label: "test signature".to_string(),
14585                documentation: None,
14586                parameters: Some(vec![lsp::ParameterInformation {
14587                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14588                    documentation: None,
14589                }]),
14590                active_parameter: None,
14591            }],
14592            active_signature: None,
14593            active_parameter: None,
14594        },
14595    );
14596    cx.update_editor(|editor, window, cx| {
14597        assert!(
14598            !editor.signature_help_state.is_shown(),
14599            "No signature help was called for"
14600        );
14601        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14602    });
14603    cx.run_until_parked();
14604    cx.update_editor(|editor, _, _| {
14605        assert!(
14606            !editor.signature_help_state.is_shown(),
14607            "No signature help should be shown when completions menu is open"
14608        );
14609    });
14610
14611    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14612        editor.context_menu_next(&Default::default(), window, cx);
14613        editor
14614            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14615            .unwrap()
14616    });
14617    cx.assert_editor_state(indoc! {"
14618        one.second_completionˇ
14619        two
14620        three
14621    "});
14622
14623    handle_resolve_completion_request(
14624        &mut cx,
14625        Some(vec![
14626            (
14627                //This overlaps with the primary completion edit which is
14628                //misbehavior from the LSP spec, test that we filter it out
14629                indoc! {"
14630                    one.second_ˇcompletion
14631                    two
14632                    threeˇ
14633                "},
14634                "overlapping additional edit",
14635            ),
14636            (
14637                indoc! {"
14638                    one.second_completion
14639                    two
14640                    threeˇ
14641                "},
14642                "\nadditional edit",
14643            ),
14644        ]),
14645    )
14646    .await;
14647    apply_additional_edits.await.unwrap();
14648    cx.assert_editor_state(indoc! {"
14649        one.second_completionˇ
14650        two
14651        three
14652        additional edit
14653    "});
14654
14655    cx.set_state(indoc! {"
14656        one.second_completion
14657        twoˇ
14658        threeˇ
14659        additional edit
14660    "});
14661    cx.simulate_keystroke(" ");
14662    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14663    cx.simulate_keystroke("s");
14664    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14665
14666    cx.assert_editor_state(indoc! {"
14667        one.second_completion
14668        two sˇ
14669        three sˇ
14670        additional edit
14671    "});
14672    handle_completion_request(
14673        indoc! {"
14674            one.second_completion
14675            two s
14676            three <s|>
14677            additional edit
14678        "},
14679        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14680        true,
14681        counter.clone(),
14682        &mut cx,
14683    )
14684    .await;
14685    cx.condition(|editor, _| editor.context_menu_visible())
14686        .await;
14687    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14688
14689    cx.simulate_keystroke("i");
14690
14691    handle_completion_request(
14692        indoc! {"
14693            one.second_completion
14694            two si
14695            three <si|>
14696            additional edit
14697        "},
14698        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14699        true,
14700        counter.clone(),
14701        &mut cx,
14702    )
14703    .await;
14704    cx.condition(|editor, _| editor.context_menu_visible())
14705        .await;
14706    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14707
14708    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14709        editor
14710            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14711            .unwrap()
14712    });
14713    cx.assert_editor_state(indoc! {"
14714        one.second_completion
14715        two sixth_completionˇ
14716        three sixth_completionˇ
14717        additional edit
14718    "});
14719
14720    apply_additional_edits.await.unwrap();
14721
14722    update_test_language_settings(&mut cx, |settings| {
14723        settings.defaults.show_completions_on_input = Some(false);
14724    });
14725    cx.set_state("editorˇ");
14726    cx.simulate_keystroke(".");
14727    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14728    cx.simulate_keystrokes("c l o");
14729    cx.assert_editor_state("editor.cloˇ");
14730    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14731    cx.update_editor(|editor, window, cx| {
14732        editor.show_completions(&ShowCompletions, window, cx);
14733    });
14734    handle_completion_request(
14735        "editor.<clo|>",
14736        vec!["close", "clobber"],
14737        true,
14738        counter.clone(),
14739        &mut cx,
14740    )
14741    .await;
14742    cx.condition(|editor, _| editor.context_menu_visible())
14743        .await;
14744    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14745
14746    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14747        editor
14748            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14749            .unwrap()
14750    });
14751    cx.assert_editor_state("editor.clobberˇ");
14752    handle_resolve_completion_request(&mut cx, None).await;
14753    apply_additional_edits.await.unwrap();
14754}
14755
14756#[gpui::test]
14757async fn test_completion_reuse(cx: &mut TestAppContext) {
14758    init_test(cx, |_| {});
14759
14760    let mut cx = EditorLspTestContext::new_rust(
14761        lsp::ServerCapabilities {
14762            completion_provider: Some(lsp::CompletionOptions {
14763                trigger_characters: Some(vec![".".to_string()]),
14764                ..Default::default()
14765            }),
14766            ..Default::default()
14767        },
14768        cx,
14769    )
14770    .await;
14771
14772    let counter = Arc::new(AtomicUsize::new(0));
14773    cx.set_state("objˇ");
14774    cx.simulate_keystroke(".");
14775
14776    // Initial completion request returns complete results
14777    let is_incomplete = false;
14778    handle_completion_request(
14779        "obj.|<>",
14780        vec!["a", "ab", "abc"],
14781        is_incomplete,
14782        counter.clone(),
14783        &mut cx,
14784    )
14785    .await;
14786    cx.run_until_parked();
14787    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14788    cx.assert_editor_state("obj.ˇ");
14789    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14790
14791    // Type "a" - filters existing completions
14792    cx.simulate_keystroke("a");
14793    cx.run_until_parked();
14794    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14795    cx.assert_editor_state("obj.aˇ");
14796    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14797
14798    // Type "b" - filters existing completions
14799    cx.simulate_keystroke("b");
14800    cx.run_until_parked();
14801    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14802    cx.assert_editor_state("obj.abˇ");
14803    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14804
14805    // Type "c" - filters existing completions
14806    cx.simulate_keystroke("c");
14807    cx.run_until_parked();
14808    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14809    cx.assert_editor_state("obj.abcˇ");
14810    check_displayed_completions(vec!["abc"], &mut cx);
14811
14812    // Backspace to delete "c" - filters existing completions
14813    cx.update_editor(|editor, window, cx| {
14814        editor.backspace(&Backspace, window, cx);
14815    });
14816    cx.run_until_parked();
14817    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14818    cx.assert_editor_state("obj.abˇ");
14819    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14820
14821    // Moving cursor to the left dismisses menu.
14822    cx.update_editor(|editor, window, cx| {
14823        editor.move_left(&MoveLeft, window, cx);
14824    });
14825    cx.run_until_parked();
14826    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14827    cx.assert_editor_state("obj.aˇb");
14828    cx.update_editor(|editor, _, _| {
14829        assert_eq!(editor.context_menu_visible(), false);
14830    });
14831
14832    // Type "b" - new request
14833    cx.simulate_keystroke("b");
14834    let is_incomplete = false;
14835    handle_completion_request(
14836        "obj.<ab|>a",
14837        vec!["ab", "abc"],
14838        is_incomplete,
14839        counter.clone(),
14840        &mut cx,
14841    )
14842    .await;
14843    cx.run_until_parked();
14844    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14845    cx.assert_editor_state("obj.abˇb");
14846    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14847
14848    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14849    cx.update_editor(|editor, window, cx| {
14850        editor.backspace(&Backspace, window, cx);
14851    });
14852    let is_incomplete = false;
14853    handle_completion_request(
14854        "obj.<a|>b",
14855        vec!["a", "ab", "abc"],
14856        is_incomplete,
14857        counter.clone(),
14858        &mut cx,
14859    )
14860    .await;
14861    cx.run_until_parked();
14862    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14863    cx.assert_editor_state("obj.aˇb");
14864    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14865
14866    // Backspace to delete "a" - dismisses menu.
14867    cx.update_editor(|editor, window, cx| {
14868        editor.backspace(&Backspace, window, cx);
14869    });
14870    cx.run_until_parked();
14871    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14872    cx.assert_editor_state("obj.ˇb");
14873    cx.update_editor(|editor, _, _| {
14874        assert_eq!(editor.context_menu_visible(), false);
14875    });
14876}
14877
14878#[gpui::test]
14879async fn test_word_completion(cx: &mut TestAppContext) {
14880    let lsp_fetch_timeout_ms = 10;
14881    init_test(cx, |language_settings| {
14882        language_settings.defaults.completions = Some(CompletionSettingsContent {
14883            words_min_length: Some(0),
14884            lsp_fetch_timeout_ms: Some(10),
14885            lsp_insert_mode: Some(LspInsertMode::Insert),
14886            ..Default::default()
14887        });
14888    });
14889
14890    let mut cx = EditorLspTestContext::new_rust(
14891        lsp::ServerCapabilities {
14892            completion_provider: Some(lsp::CompletionOptions {
14893                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14894                ..lsp::CompletionOptions::default()
14895            }),
14896            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14897            ..lsp::ServerCapabilities::default()
14898        },
14899        cx,
14900    )
14901    .await;
14902
14903    let throttle_completions = Arc::new(AtomicBool::new(false));
14904
14905    let lsp_throttle_completions = throttle_completions.clone();
14906    let _completion_requests_handler =
14907        cx.lsp
14908            .server
14909            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14910                let lsp_throttle_completions = lsp_throttle_completions.clone();
14911                let cx = cx.clone();
14912                async move {
14913                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14914                        cx.background_executor()
14915                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14916                            .await;
14917                    }
14918                    Ok(Some(lsp::CompletionResponse::Array(vec![
14919                        lsp::CompletionItem {
14920                            label: "first".into(),
14921                            ..lsp::CompletionItem::default()
14922                        },
14923                        lsp::CompletionItem {
14924                            label: "last".into(),
14925                            ..lsp::CompletionItem::default()
14926                        },
14927                    ])))
14928                }
14929            });
14930
14931    cx.set_state(indoc! {"
14932        oneˇ
14933        two
14934        three
14935    "});
14936    cx.simulate_keystroke(".");
14937    cx.executor().run_until_parked();
14938    cx.condition(|editor, _| editor.context_menu_visible())
14939        .await;
14940    cx.update_editor(|editor, window, cx| {
14941        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14942        {
14943            assert_eq!(
14944                completion_menu_entries(menu),
14945                &["first", "last"],
14946                "When LSP server is fast to reply, no fallback word completions are used"
14947            );
14948        } else {
14949            panic!("expected completion menu to be open");
14950        }
14951        editor.cancel(&Cancel, window, cx);
14952    });
14953    cx.executor().run_until_parked();
14954    cx.condition(|editor, _| !editor.context_menu_visible())
14955        .await;
14956
14957    throttle_completions.store(true, atomic::Ordering::Release);
14958    cx.simulate_keystroke(".");
14959    cx.executor()
14960        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14961    cx.executor().run_until_parked();
14962    cx.condition(|editor, _| editor.context_menu_visible())
14963        .await;
14964    cx.update_editor(|editor, _, _| {
14965        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14966        {
14967            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14968                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14969        } else {
14970            panic!("expected completion menu to be open");
14971        }
14972    });
14973}
14974
14975#[gpui::test]
14976async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14977    init_test(cx, |language_settings| {
14978        language_settings.defaults.completions = Some(CompletionSettingsContent {
14979            words: Some(WordsCompletionMode::Enabled),
14980            words_min_length: Some(0),
14981            lsp_insert_mode: Some(LspInsertMode::Insert),
14982            ..Default::default()
14983        });
14984    });
14985
14986    let mut cx = EditorLspTestContext::new_rust(
14987        lsp::ServerCapabilities {
14988            completion_provider: Some(lsp::CompletionOptions {
14989                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14990                ..lsp::CompletionOptions::default()
14991            }),
14992            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14993            ..lsp::ServerCapabilities::default()
14994        },
14995        cx,
14996    )
14997    .await;
14998
14999    let _completion_requests_handler =
15000        cx.lsp
15001            .server
15002            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15003                Ok(Some(lsp::CompletionResponse::Array(vec![
15004                    lsp::CompletionItem {
15005                        label: "first".into(),
15006                        ..lsp::CompletionItem::default()
15007                    },
15008                    lsp::CompletionItem {
15009                        label: "last".into(),
15010                        ..lsp::CompletionItem::default()
15011                    },
15012                ])))
15013            });
15014
15015    cx.set_state(indoc! {"ˇ
15016        first
15017        last
15018        second
15019    "});
15020    cx.simulate_keystroke(".");
15021    cx.executor().run_until_parked();
15022    cx.condition(|editor, _| editor.context_menu_visible())
15023        .await;
15024    cx.update_editor(|editor, _, _| {
15025        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15026        {
15027            assert_eq!(
15028                completion_menu_entries(menu),
15029                &["first", "last", "second"],
15030                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15031            );
15032        } else {
15033            panic!("expected completion menu to be open");
15034        }
15035    });
15036}
15037
15038#[gpui::test]
15039async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15040    init_test(cx, |language_settings| {
15041        language_settings.defaults.completions = Some(CompletionSettingsContent {
15042            words: Some(WordsCompletionMode::Disabled),
15043            words_min_length: Some(0),
15044            lsp_insert_mode: Some(LspInsertMode::Insert),
15045            ..Default::default()
15046        });
15047    });
15048
15049    let mut cx = EditorLspTestContext::new_rust(
15050        lsp::ServerCapabilities {
15051            completion_provider: Some(lsp::CompletionOptions {
15052                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15053                ..lsp::CompletionOptions::default()
15054            }),
15055            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15056            ..lsp::ServerCapabilities::default()
15057        },
15058        cx,
15059    )
15060    .await;
15061
15062    let _completion_requests_handler =
15063        cx.lsp
15064            .server
15065            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15066                panic!("LSP completions should not be queried when dealing with word completions")
15067            });
15068
15069    cx.set_state(indoc! {"ˇ
15070        first
15071        last
15072        second
15073    "});
15074    cx.update_editor(|editor, window, cx| {
15075        editor.show_word_completions(&ShowWordCompletions, window, cx);
15076    });
15077    cx.executor().run_until_parked();
15078    cx.condition(|editor, _| editor.context_menu_visible())
15079        .await;
15080    cx.update_editor(|editor, _, _| {
15081        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15082        {
15083            assert_eq!(
15084                completion_menu_entries(menu),
15085                &["first", "last", "second"],
15086                "`ShowWordCompletions` action should show word completions"
15087            );
15088        } else {
15089            panic!("expected completion menu to be open");
15090        }
15091    });
15092
15093    cx.simulate_keystroke("l");
15094    cx.executor().run_until_parked();
15095    cx.condition(|editor, _| editor.context_menu_visible())
15096        .await;
15097    cx.update_editor(|editor, _, _| {
15098        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15099        {
15100            assert_eq!(
15101                completion_menu_entries(menu),
15102                &["last"],
15103                "After showing word completions, further editing should filter them and not query the LSP"
15104            );
15105        } else {
15106            panic!("expected completion menu to be open");
15107        }
15108    });
15109}
15110
15111#[gpui::test]
15112async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15113    init_test(cx, |language_settings| {
15114        language_settings.defaults.completions = Some(CompletionSettingsContent {
15115            words_min_length: Some(0),
15116            lsp: Some(false),
15117            lsp_insert_mode: Some(LspInsertMode::Insert),
15118            ..Default::default()
15119        });
15120    });
15121
15122    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15123
15124    cx.set_state(indoc! {"ˇ
15125        0_usize
15126        let
15127        33
15128        4.5f32
15129    "});
15130    cx.update_editor(|editor, window, cx| {
15131        editor.show_completions(&ShowCompletions, window, cx);
15132    });
15133    cx.executor().run_until_parked();
15134    cx.condition(|editor, _| editor.context_menu_visible())
15135        .await;
15136    cx.update_editor(|editor, window, cx| {
15137        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15138        {
15139            assert_eq!(
15140                completion_menu_entries(menu),
15141                &["let"],
15142                "With no digits in the completion query, no digits should be in the word completions"
15143            );
15144        } else {
15145            panic!("expected completion menu to be open");
15146        }
15147        editor.cancel(&Cancel, window, cx);
15148    });
15149
15150    cx.set_state(indoc! {"15151        0_usize
15152        let
15153        3
15154        33.35f32
15155    "});
15156    cx.update_editor(|editor, window, cx| {
15157        editor.show_completions(&ShowCompletions, window, cx);
15158    });
15159    cx.executor().run_until_parked();
15160    cx.condition(|editor, _| editor.context_menu_visible())
15161        .await;
15162    cx.update_editor(|editor, _, _| {
15163        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15164        {
15165            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15166                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15167        } else {
15168            panic!("expected completion menu to be open");
15169        }
15170    });
15171}
15172
15173#[gpui::test]
15174async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15175    init_test(cx, |language_settings| {
15176        language_settings.defaults.completions = Some(CompletionSettingsContent {
15177            words: Some(WordsCompletionMode::Enabled),
15178            words_min_length: Some(3),
15179            lsp_insert_mode: Some(LspInsertMode::Insert),
15180            ..Default::default()
15181        });
15182    });
15183
15184    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15185    cx.set_state(indoc! {"ˇ
15186        wow
15187        wowen
15188        wowser
15189    "});
15190    cx.simulate_keystroke("w");
15191    cx.executor().run_until_parked();
15192    cx.update_editor(|editor, _, _| {
15193        if editor.context_menu.borrow_mut().is_some() {
15194            panic!(
15195                "expected completion menu to be hidden, as words completion threshold is not met"
15196            );
15197        }
15198    });
15199
15200    cx.update_editor(|editor, window, cx| {
15201        editor.show_word_completions(&ShowWordCompletions, window, cx);
15202    });
15203    cx.executor().run_until_parked();
15204    cx.update_editor(|editor, window, cx| {
15205        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15206        {
15207            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");
15208        } else {
15209            panic!("expected completion menu to be open after the word completions are called with an action");
15210        }
15211
15212        editor.cancel(&Cancel, window, cx);
15213    });
15214    cx.update_editor(|editor, _, _| {
15215        if editor.context_menu.borrow_mut().is_some() {
15216            panic!("expected completion menu to be hidden after canceling");
15217        }
15218    });
15219
15220    cx.simulate_keystroke("o");
15221    cx.executor().run_until_parked();
15222    cx.update_editor(|editor, _, _| {
15223        if editor.context_menu.borrow_mut().is_some() {
15224            panic!(
15225                "expected completion menu to be hidden, as words completion threshold is not met still"
15226            );
15227        }
15228    });
15229
15230    cx.simulate_keystroke("w");
15231    cx.executor().run_until_parked();
15232    cx.update_editor(|editor, _, _| {
15233        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15234        {
15235            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15236        } else {
15237            panic!("expected completion menu to be open after the word completions threshold is met");
15238        }
15239    });
15240}
15241
15242#[gpui::test]
15243async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15244    init_test(cx, |language_settings| {
15245        language_settings.defaults.completions = Some(CompletionSettingsContent {
15246            words: Some(WordsCompletionMode::Enabled),
15247            words_min_length: Some(0),
15248            lsp_insert_mode: Some(LspInsertMode::Insert),
15249            ..Default::default()
15250        });
15251    });
15252
15253    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15254    cx.update_editor(|editor, _, _| {
15255        editor.disable_word_completions();
15256    });
15257    cx.set_state(indoc! {"ˇ
15258        wow
15259        wowen
15260        wowser
15261    "});
15262    cx.simulate_keystroke("w");
15263    cx.executor().run_until_parked();
15264    cx.update_editor(|editor, _, _| {
15265        if editor.context_menu.borrow_mut().is_some() {
15266            panic!(
15267                "expected completion menu to be hidden, as words completion are disabled for this editor"
15268            );
15269        }
15270    });
15271
15272    cx.update_editor(|editor, window, cx| {
15273        editor.show_word_completions(&ShowWordCompletions, window, cx);
15274    });
15275    cx.executor().run_until_parked();
15276    cx.update_editor(|editor, _, _| {
15277        if editor.context_menu.borrow_mut().is_some() {
15278            panic!(
15279                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15280            );
15281        }
15282    });
15283}
15284
15285#[gpui::test]
15286async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15287    init_test(cx, |language_settings| {
15288        language_settings.defaults.completions = Some(CompletionSettingsContent {
15289            words: Some(WordsCompletionMode::Disabled),
15290            words_min_length: Some(0),
15291            lsp_insert_mode: Some(LspInsertMode::Insert),
15292            ..Default::default()
15293        });
15294    });
15295
15296    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15297    cx.update_editor(|editor, _, _| {
15298        editor.set_completion_provider(None);
15299    });
15300    cx.set_state(indoc! {"ˇ
15301        wow
15302        wowen
15303        wowser
15304    "});
15305    cx.simulate_keystroke("w");
15306    cx.executor().run_until_parked();
15307    cx.update_editor(|editor, _, _| {
15308        if editor.context_menu.borrow_mut().is_some() {
15309            panic!("expected completion menu to be hidden, as disabled in settings");
15310        }
15311    });
15312}
15313
15314fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15315    let position = || lsp::Position {
15316        line: params.text_document_position.position.line,
15317        character: params.text_document_position.position.character,
15318    };
15319    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15320        range: lsp::Range {
15321            start: position(),
15322            end: position(),
15323        },
15324        new_text: text.to_string(),
15325    }))
15326}
15327
15328#[gpui::test]
15329async fn test_multiline_completion(cx: &mut TestAppContext) {
15330    init_test(cx, |_| {});
15331
15332    let fs = FakeFs::new(cx.executor());
15333    fs.insert_tree(
15334        path!("/a"),
15335        json!({
15336            "main.ts": "a",
15337        }),
15338    )
15339    .await;
15340
15341    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15342    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15343    let typescript_language = Arc::new(Language::new(
15344        LanguageConfig {
15345            name: "TypeScript".into(),
15346            matcher: LanguageMatcher {
15347                path_suffixes: vec!["ts".to_string()],
15348                ..LanguageMatcher::default()
15349            },
15350            line_comments: vec!["// ".into()],
15351            ..LanguageConfig::default()
15352        },
15353        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15354    ));
15355    language_registry.add(typescript_language.clone());
15356    let mut fake_servers = language_registry.register_fake_lsp(
15357        "TypeScript",
15358        FakeLspAdapter {
15359            capabilities: lsp::ServerCapabilities {
15360                completion_provider: Some(lsp::CompletionOptions {
15361                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15362                    ..lsp::CompletionOptions::default()
15363                }),
15364                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15365                ..lsp::ServerCapabilities::default()
15366            },
15367            // Emulate vtsls label generation
15368            label_for_completion: Some(Box::new(|item, _| {
15369                let text = if let Some(description) = item
15370                    .label_details
15371                    .as_ref()
15372                    .and_then(|label_details| label_details.description.as_ref())
15373                {
15374                    format!("{} {}", item.label, description)
15375                } else if let Some(detail) = &item.detail {
15376                    format!("{} {}", item.label, detail)
15377                } else {
15378                    item.label.clone()
15379                };
15380                Some(language::CodeLabel::plain(text, None))
15381            })),
15382            ..FakeLspAdapter::default()
15383        },
15384    );
15385    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15386    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15387    let worktree_id = workspace
15388        .update(cx, |workspace, _window, cx| {
15389            workspace.project().update(cx, |project, cx| {
15390                project.worktrees(cx).next().unwrap().read(cx).id()
15391            })
15392        })
15393        .unwrap();
15394    let _buffer = project
15395        .update(cx, |project, cx| {
15396            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15397        })
15398        .await
15399        .unwrap();
15400    let editor = workspace
15401        .update(cx, |workspace, window, cx| {
15402            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15403        })
15404        .unwrap()
15405        .await
15406        .unwrap()
15407        .downcast::<Editor>()
15408        .unwrap();
15409    let fake_server = fake_servers.next().await.unwrap();
15410
15411    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15412    let multiline_label_2 = "a\nb\nc\n";
15413    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15414    let multiline_description = "d\ne\nf\n";
15415    let multiline_detail_2 = "g\nh\ni\n";
15416
15417    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15418        move |params, _| async move {
15419            Ok(Some(lsp::CompletionResponse::Array(vec![
15420                lsp::CompletionItem {
15421                    label: multiline_label.to_string(),
15422                    text_edit: gen_text_edit(&params, "new_text_1"),
15423                    ..lsp::CompletionItem::default()
15424                },
15425                lsp::CompletionItem {
15426                    label: "single line label 1".to_string(),
15427                    detail: Some(multiline_detail.to_string()),
15428                    text_edit: gen_text_edit(&params, "new_text_2"),
15429                    ..lsp::CompletionItem::default()
15430                },
15431                lsp::CompletionItem {
15432                    label: "single line label 2".to_string(),
15433                    label_details: Some(lsp::CompletionItemLabelDetails {
15434                        description: Some(multiline_description.to_string()),
15435                        detail: None,
15436                    }),
15437                    text_edit: gen_text_edit(&params, "new_text_2"),
15438                    ..lsp::CompletionItem::default()
15439                },
15440                lsp::CompletionItem {
15441                    label: multiline_label_2.to_string(),
15442                    detail: Some(multiline_detail_2.to_string()),
15443                    text_edit: gen_text_edit(&params, "new_text_3"),
15444                    ..lsp::CompletionItem::default()
15445                },
15446                lsp::CompletionItem {
15447                    label: "Label with many     spaces and \t but without newlines".to_string(),
15448                    detail: Some(
15449                        "Details with many     spaces and \t but without newlines".to_string(),
15450                    ),
15451                    text_edit: gen_text_edit(&params, "new_text_4"),
15452                    ..lsp::CompletionItem::default()
15453                },
15454            ])))
15455        },
15456    );
15457
15458    editor.update_in(cx, |editor, window, cx| {
15459        cx.focus_self(window);
15460        editor.move_to_end(&MoveToEnd, window, cx);
15461        editor.handle_input(".", window, cx);
15462    });
15463    cx.run_until_parked();
15464    completion_handle.next().await.unwrap();
15465
15466    editor.update(cx, |editor, _| {
15467        assert!(editor.context_menu_visible());
15468        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15469        {
15470            let completion_labels = menu
15471                .completions
15472                .borrow()
15473                .iter()
15474                .map(|c| c.label.text.clone())
15475                .collect::<Vec<_>>();
15476            assert_eq!(
15477                completion_labels,
15478                &[
15479                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15480                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15481                    "single line label 2 d e f ",
15482                    "a b c g h i ",
15483                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15484                ],
15485                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15486            );
15487
15488            for completion in menu
15489                .completions
15490                .borrow()
15491                .iter() {
15492                    assert_eq!(
15493                        completion.label.filter_range,
15494                        0..completion.label.text.len(),
15495                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15496                    );
15497                }
15498        } else {
15499            panic!("expected completion menu to be open");
15500        }
15501    });
15502}
15503
15504#[gpui::test]
15505async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15506    init_test(cx, |_| {});
15507    let mut cx = EditorLspTestContext::new_rust(
15508        lsp::ServerCapabilities {
15509            completion_provider: Some(lsp::CompletionOptions {
15510                trigger_characters: Some(vec![".".to_string()]),
15511                ..Default::default()
15512            }),
15513            ..Default::default()
15514        },
15515        cx,
15516    )
15517    .await;
15518    cx.lsp
15519        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15520            Ok(Some(lsp::CompletionResponse::Array(vec![
15521                lsp::CompletionItem {
15522                    label: "first".into(),
15523                    ..Default::default()
15524                },
15525                lsp::CompletionItem {
15526                    label: "last".into(),
15527                    ..Default::default()
15528                },
15529            ])))
15530        });
15531    cx.set_state("variableˇ");
15532    cx.simulate_keystroke(".");
15533    cx.executor().run_until_parked();
15534
15535    cx.update_editor(|editor, _, _| {
15536        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15537        {
15538            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15539        } else {
15540            panic!("expected completion menu to be open");
15541        }
15542    });
15543
15544    cx.update_editor(|editor, window, cx| {
15545        editor.move_page_down(&MovePageDown::default(), window, cx);
15546        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15547        {
15548            assert!(
15549                menu.selected_item == 1,
15550                "expected PageDown to select the last item from the context menu"
15551            );
15552        } else {
15553            panic!("expected completion menu to stay open after PageDown");
15554        }
15555    });
15556
15557    cx.update_editor(|editor, window, cx| {
15558        editor.move_page_up(&MovePageUp::default(), window, cx);
15559        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15560        {
15561            assert!(
15562                menu.selected_item == 0,
15563                "expected PageUp to select the first item from the context menu"
15564            );
15565        } else {
15566            panic!("expected completion menu to stay open after PageUp");
15567        }
15568    });
15569}
15570
15571#[gpui::test]
15572async fn test_as_is_completions(cx: &mut TestAppContext) {
15573    init_test(cx, |_| {});
15574    let mut cx = EditorLspTestContext::new_rust(
15575        lsp::ServerCapabilities {
15576            completion_provider: Some(lsp::CompletionOptions {
15577                ..Default::default()
15578            }),
15579            ..Default::default()
15580        },
15581        cx,
15582    )
15583    .await;
15584    cx.lsp
15585        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15586            Ok(Some(lsp::CompletionResponse::Array(vec![
15587                lsp::CompletionItem {
15588                    label: "unsafe".into(),
15589                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15590                        range: lsp::Range {
15591                            start: lsp::Position {
15592                                line: 1,
15593                                character: 2,
15594                            },
15595                            end: lsp::Position {
15596                                line: 1,
15597                                character: 3,
15598                            },
15599                        },
15600                        new_text: "unsafe".to_string(),
15601                    })),
15602                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15603                    ..Default::default()
15604                },
15605            ])))
15606        });
15607    cx.set_state("fn a() {}\n");
15608    cx.executor().run_until_parked();
15609    cx.update_editor(|editor, window, cx| {
15610        editor.trigger_completion_on_input("n", true, window, cx)
15611    });
15612    cx.executor().run_until_parked();
15613
15614    cx.update_editor(|editor, window, cx| {
15615        editor.confirm_completion(&Default::default(), window, cx)
15616    });
15617    cx.executor().run_until_parked();
15618    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15619}
15620
15621#[gpui::test]
15622async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15623    init_test(cx, |_| {});
15624    let language =
15625        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15626    let mut cx = EditorLspTestContext::new(
15627        language,
15628        lsp::ServerCapabilities {
15629            completion_provider: Some(lsp::CompletionOptions {
15630                ..lsp::CompletionOptions::default()
15631            }),
15632            ..lsp::ServerCapabilities::default()
15633        },
15634        cx,
15635    )
15636    .await;
15637
15638    cx.set_state(
15639        "#ifndef BAR_H
15640#define BAR_H
15641
15642#include <stdbool.h>
15643
15644int fn_branch(bool do_branch1, bool do_branch2);
15645
15646#endif // BAR_H
15647ˇ",
15648    );
15649    cx.executor().run_until_parked();
15650    cx.update_editor(|editor, window, cx| {
15651        editor.handle_input("#", window, cx);
15652    });
15653    cx.executor().run_until_parked();
15654    cx.update_editor(|editor, window, cx| {
15655        editor.handle_input("i", window, cx);
15656    });
15657    cx.executor().run_until_parked();
15658    cx.update_editor(|editor, window, cx| {
15659        editor.handle_input("n", window, cx);
15660    });
15661    cx.executor().run_until_parked();
15662    cx.assert_editor_state(
15663        "#ifndef BAR_H
15664#define BAR_H
15665
15666#include <stdbool.h>
15667
15668int fn_branch(bool do_branch1, bool do_branch2);
15669
15670#endif // BAR_H
15671#inˇ",
15672    );
15673
15674    cx.lsp
15675        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15676            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15677                is_incomplete: false,
15678                item_defaults: None,
15679                items: vec![lsp::CompletionItem {
15680                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15681                    label_details: Some(lsp::CompletionItemLabelDetails {
15682                        detail: Some("header".to_string()),
15683                        description: None,
15684                    }),
15685                    label: " include".to_string(),
15686                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15687                        range: lsp::Range {
15688                            start: lsp::Position {
15689                                line: 8,
15690                                character: 1,
15691                            },
15692                            end: lsp::Position {
15693                                line: 8,
15694                                character: 1,
15695                            },
15696                        },
15697                        new_text: "include \"$0\"".to_string(),
15698                    })),
15699                    sort_text: Some("40b67681include".to_string()),
15700                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15701                    filter_text: Some("include".to_string()),
15702                    insert_text: Some("include \"$0\"".to_string()),
15703                    ..lsp::CompletionItem::default()
15704                }],
15705            })))
15706        });
15707    cx.update_editor(|editor, window, cx| {
15708        editor.show_completions(&ShowCompletions, window, cx);
15709    });
15710    cx.executor().run_until_parked();
15711    cx.update_editor(|editor, window, cx| {
15712        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15713    });
15714    cx.executor().run_until_parked();
15715    cx.assert_editor_state(
15716        "#ifndef BAR_H
15717#define BAR_H
15718
15719#include <stdbool.h>
15720
15721int fn_branch(bool do_branch1, bool do_branch2);
15722
15723#endif // BAR_H
15724#include \"ˇ\"",
15725    );
15726
15727    cx.lsp
15728        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15729            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15730                is_incomplete: true,
15731                item_defaults: None,
15732                items: vec![lsp::CompletionItem {
15733                    kind: Some(lsp::CompletionItemKind::FILE),
15734                    label: "AGL/".to_string(),
15735                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15736                        range: lsp::Range {
15737                            start: lsp::Position {
15738                                line: 8,
15739                                character: 10,
15740                            },
15741                            end: lsp::Position {
15742                                line: 8,
15743                                character: 11,
15744                            },
15745                        },
15746                        new_text: "AGL/".to_string(),
15747                    })),
15748                    sort_text: Some("40b67681AGL/".to_string()),
15749                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15750                    filter_text: Some("AGL/".to_string()),
15751                    insert_text: Some("AGL/".to_string()),
15752                    ..lsp::CompletionItem::default()
15753                }],
15754            })))
15755        });
15756    cx.update_editor(|editor, window, cx| {
15757        editor.show_completions(&ShowCompletions, window, cx);
15758    });
15759    cx.executor().run_until_parked();
15760    cx.update_editor(|editor, window, cx| {
15761        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15762    });
15763    cx.executor().run_until_parked();
15764    cx.assert_editor_state(
15765        r##"#ifndef BAR_H
15766#define BAR_H
15767
15768#include <stdbool.h>
15769
15770int fn_branch(bool do_branch1, bool do_branch2);
15771
15772#endif // BAR_H
15773#include "AGL/ˇ"##,
15774    );
15775
15776    cx.update_editor(|editor, window, cx| {
15777        editor.handle_input("\"", window, cx);
15778    });
15779    cx.executor().run_until_parked();
15780    cx.assert_editor_state(
15781        r##"#ifndef BAR_H
15782#define BAR_H
15783
15784#include <stdbool.h>
15785
15786int fn_branch(bool do_branch1, bool do_branch2);
15787
15788#endif // BAR_H
15789#include "AGL/"ˇ"##,
15790    );
15791}
15792
15793#[gpui::test]
15794async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15795    init_test(cx, |_| {});
15796
15797    let mut cx = EditorLspTestContext::new_rust(
15798        lsp::ServerCapabilities {
15799            completion_provider: Some(lsp::CompletionOptions {
15800                trigger_characters: Some(vec![".".to_string()]),
15801                resolve_provider: Some(true),
15802                ..Default::default()
15803            }),
15804            ..Default::default()
15805        },
15806        cx,
15807    )
15808    .await;
15809
15810    cx.set_state("fn main() { let a = 2ˇ; }");
15811    cx.simulate_keystroke(".");
15812    let completion_item = lsp::CompletionItem {
15813        label: "Some".into(),
15814        kind: Some(lsp::CompletionItemKind::SNIPPET),
15815        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15816        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15817            kind: lsp::MarkupKind::Markdown,
15818            value: "```rust\nSome(2)\n```".to_string(),
15819        })),
15820        deprecated: Some(false),
15821        sort_text: Some("Some".to_string()),
15822        filter_text: Some("Some".to_string()),
15823        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15824        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15825            range: lsp::Range {
15826                start: lsp::Position {
15827                    line: 0,
15828                    character: 22,
15829                },
15830                end: lsp::Position {
15831                    line: 0,
15832                    character: 22,
15833                },
15834            },
15835            new_text: "Some(2)".to_string(),
15836        })),
15837        additional_text_edits: Some(vec![lsp::TextEdit {
15838            range: lsp::Range {
15839                start: lsp::Position {
15840                    line: 0,
15841                    character: 20,
15842                },
15843                end: lsp::Position {
15844                    line: 0,
15845                    character: 22,
15846                },
15847            },
15848            new_text: "".to_string(),
15849        }]),
15850        ..Default::default()
15851    };
15852
15853    let closure_completion_item = completion_item.clone();
15854    let counter = Arc::new(AtomicUsize::new(0));
15855    let counter_clone = counter.clone();
15856    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15857        let task_completion_item = closure_completion_item.clone();
15858        counter_clone.fetch_add(1, atomic::Ordering::Release);
15859        async move {
15860            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15861                is_incomplete: true,
15862                item_defaults: None,
15863                items: vec![task_completion_item],
15864            })))
15865        }
15866    });
15867
15868    cx.condition(|editor, _| editor.context_menu_visible())
15869        .await;
15870    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15871    assert!(request.next().await.is_some());
15872    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15873
15874    cx.simulate_keystrokes("S o m");
15875    cx.condition(|editor, _| editor.context_menu_visible())
15876        .await;
15877    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15878    assert!(request.next().await.is_some());
15879    assert!(request.next().await.is_some());
15880    assert!(request.next().await.is_some());
15881    request.close();
15882    assert!(request.next().await.is_none());
15883    assert_eq!(
15884        counter.load(atomic::Ordering::Acquire),
15885        4,
15886        "With the completions menu open, only one LSP request should happen per input"
15887    );
15888}
15889
15890#[gpui::test]
15891async fn test_toggle_comment(cx: &mut TestAppContext) {
15892    init_test(cx, |_| {});
15893    let mut cx = EditorTestContext::new(cx).await;
15894    let language = Arc::new(Language::new(
15895        LanguageConfig {
15896            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15897            ..Default::default()
15898        },
15899        Some(tree_sitter_rust::LANGUAGE.into()),
15900    ));
15901    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15902
15903    // If multiple selections intersect a line, the line is only toggled once.
15904    cx.set_state(indoc! {"
15905        fn a() {
15906            «//b();
15907            ˇ»// «c();
15908            //ˇ»  d();
15909        }
15910    "});
15911
15912    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15913
15914    cx.assert_editor_state(indoc! {"
15915        fn a() {
15916            «b();
15917            c();
15918            ˇ» d();
15919        }
15920    "});
15921
15922    // The comment prefix is inserted at the same column for every line in a
15923    // selection.
15924    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15925
15926    cx.assert_editor_state(indoc! {"
15927        fn a() {
15928            // «b();
15929            // c();
15930            ˇ»//  d();
15931        }
15932    "});
15933
15934    // If a selection ends at the beginning of a line, that line is not toggled.
15935    cx.set_selections_state(indoc! {"
15936        fn a() {
15937            // b();
15938            «// c();
15939        ˇ»    //  d();
15940        }
15941    "});
15942
15943    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15944
15945    cx.assert_editor_state(indoc! {"
15946        fn a() {
15947            // b();
15948            «c();
15949        ˇ»    //  d();
15950        }
15951    "});
15952
15953    // If a selection span a single line and is empty, the line is toggled.
15954    cx.set_state(indoc! {"
15955        fn a() {
15956            a();
15957            b();
15958        ˇ
15959        }
15960    "});
15961
15962    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15963
15964    cx.assert_editor_state(indoc! {"
15965        fn a() {
15966            a();
15967            b();
15968        //•ˇ
15969        }
15970    "});
15971
15972    // If a selection span multiple lines, empty lines are not toggled.
15973    cx.set_state(indoc! {"
15974        fn a() {
15975            «a();
15976
15977            c();ˇ»
15978        }
15979    "});
15980
15981    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15982
15983    cx.assert_editor_state(indoc! {"
15984        fn a() {
15985            // «a();
15986
15987            // c();ˇ»
15988        }
15989    "});
15990
15991    // If a selection includes multiple comment prefixes, all lines are uncommented.
15992    cx.set_state(indoc! {"
15993        fn a() {
15994            «// a();
15995            /// b();
15996            //! c();ˇ»
15997        }
15998    "});
15999
16000    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16001
16002    cx.assert_editor_state(indoc! {"
16003        fn a() {
16004            «a();
16005            b();
16006            c();ˇ»
16007        }
16008    "});
16009}
16010
16011#[gpui::test]
16012async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16013    init_test(cx, |_| {});
16014    let mut cx = EditorTestContext::new(cx).await;
16015    let language = Arc::new(Language::new(
16016        LanguageConfig {
16017            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16018            ..Default::default()
16019        },
16020        Some(tree_sitter_rust::LANGUAGE.into()),
16021    ));
16022    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16023
16024    let toggle_comments = &ToggleComments {
16025        advance_downwards: false,
16026        ignore_indent: true,
16027    };
16028
16029    // If multiple selections intersect a line, the line is only toggled once.
16030    cx.set_state(indoc! {"
16031        fn a() {
16032        //    «b();
16033        //    c();
16034        //    ˇ» d();
16035        }
16036    "});
16037
16038    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16039
16040    cx.assert_editor_state(indoc! {"
16041        fn a() {
16042            «b();
16043            c();
16044            ˇ» d();
16045        }
16046    "});
16047
16048    // The comment prefix is inserted at the beginning of each line
16049    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16050
16051    cx.assert_editor_state(indoc! {"
16052        fn a() {
16053        //    «b();
16054        //    c();
16055        //    ˇ» d();
16056        }
16057    "});
16058
16059    // If a selection ends at the beginning of a line, that line is not toggled.
16060    cx.set_selections_state(indoc! {"
16061        fn a() {
16062        //    b();
16063        //    «c();
16064        ˇ»//     d();
16065        }
16066    "});
16067
16068    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16069
16070    cx.assert_editor_state(indoc! {"
16071        fn a() {
16072        //    b();
16073            «c();
16074        ˇ»//     d();
16075        }
16076    "});
16077
16078    // If a selection span a single line and is empty, the line is toggled.
16079    cx.set_state(indoc! {"
16080        fn a() {
16081            a();
16082            b();
16083        ˇ
16084        }
16085    "});
16086
16087    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16088
16089    cx.assert_editor_state(indoc! {"
16090        fn a() {
16091            a();
16092            b();
16093        //ˇ
16094        }
16095    "});
16096
16097    // If a selection span multiple lines, empty lines are not toggled.
16098    cx.set_state(indoc! {"
16099        fn a() {
16100            «a();
16101
16102            c();ˇ»
16103        }
16104    "});
16105
16106    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16107
16108    cx.assert_editor_state(indoc! {"
16109        fn a() {
16110        //    «a();
16111
16112        //    c();ˇ»
16113        }
16114    "});
16115
16116    // If a selection includes multiple comment prefixes, all lines are uncommented.
16117    cx.set_state(indoc! {"
16118        fn a() {
16119        //    «a();
16120        ///    b();
16121        //!    c();ˇ»
16122        }
16123    "});
16124
16125    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16126
16127    cx.assert_editor_state(indoc! {"
16128        fn a() {
16129            «a();
16130            b();
16131            c();ˇ»
16132        }
16133    "});
16134}
16135
16136#[gpui::test]
16137async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16138    init_test(cx, |_| {});
16139
16140    let language = Arc::new(Language::new(
16141        LanguageConfig {
16142            line_comments: vec!["// ".into()],
16143            ..Default::default()
16144        },
16145        Some(tree_sitter_rust::LANGUAGE.into()),
16146    ));
16147
16148    let mut cx = EditorTestContext::new(cx).await;
16149
16150    cx.language_registry().add(language.clone());
16151    cx.update_buffer(|buffer, cx| {
16152        buffer.set_language(Some(language), cx);
16153    });
16154
16155    let toggle_comments = &ToggleComments {
16156        advance_downwards: true,
16157        ignore_indent: false,
16158    };
16159
16160    // Single cursor on one line -> advance
16161    // Cursor moves horizontally 3 characters as well on non-blank line
16162    cx.set_state(indoc!(
16163        "fn a() {
16164             ˇdog();
16165             cat();
16166        }"
16167    ));
16168    cx.update_editor(|editor, window, cx| {
16169        editor.toggle_comments(toggle_comments, window, cx);
16170    });
16171    cx.assert_editor_state(indoc!(
16172        "fn a() {
16173             // dog();
16174             catˇ();
16175        }"
16176    ));
16177
16178    // Single selection on one line -> don't advance
16179    cx.set_state(indoc!(
16180        "fn a() {
16181             «dog()ˇ»;
16182             cat();
16183        }"
16184    ));
16185    cx.update_editor(|editor, window, cx| {
16186        editor.toggle_comments(toggle_comments, window, cx);
16187    });
16188    cx.assert_editor_state(indoc!(
16189        "fn a() {
16190             // «dog()ˇ»;
16191             cat();
16192        }"
16193    ));
16194
16195    // Multiple cursors on one line -> advance
16196    cx.set_state(indoc!(
16197        "fn a() {
16198             ˇdˇog();
16199             cat();
16200        }"
16201    ));
16202    cx.update_editor(|editor, window, cx| {
16203        editor.toggle_comments(toggle_comments, window, cx);
16204    });
16205    cx.assert_editor_state(indoc!(
16206        "fn a() {
16207             // dog();
16208             catˇ(ˇ);
16209        }"
16210    ));
16211
16212    // Multiple cursors on one line, with selection -> don't advance
16213    cx.set_state(indoc!(
16214        "fn a() {
16215             ˇdˇog«()ˇ»;
16216             cat();
16217        }"
16218    ));
16219    cx.update_editor(|editor, window, cx| {
16220        editor.toggle_comments(toggle_comments, window, cx);
16221    });
16222    cx.assert_editor_state(indoc!(
16223        "fn a() {
16224             // ˇdˇog«()ˇ»;
16225             cat();
16226        }"
16227    ));
16228
16229    // Single cursor on one line -> advance
16230    // Cursor moves to column 0 on blank line
16231    cx.set_state(indoc!(
16232        "fn a() {
16233             ˇdog();
16234
16235             cat();
16236        }"
16237    ));
16238    cx.update_editor(|editor, window, cx| {
16239        editor.toggle_comments(toggle_comments, window, cx);
16240    });
16241    cx.assert_editor_state(indoc!(
16242        "fn a() {
16243             // dog();
16244        ˇ
16245             cat();
16246        }"
16247    ));
16248
16249    // Single cursor on one line -> advance
16250    // Cursor starts and ends at column 0
16251    cx.set_state(indoc!(
16252        "fn a() {
16253         ˇ    dog();
16254             cat();
16255        }"
16256    ));
16257    cx.update_editor(|editor, window, cx| {
16258        editor.toggle_comments(toggle_comments, window, cx);
16259    });
16260    cx.assert_editor_state(indoc!(
16261        "fn a() {
16262             // dog();
16263         ˇ    cat();
16264        }"
16265    ));
16266}
16267
16268#[gpui::test]
16269async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16270    init_test(cx, |_| {});
16271
16272    let mut cx = EditorTestContext::new(cx).await;
16273
16274    let html_language = Arc::new(
16275        Language::new(
16276            LanguageConfig {
16277                name: "HTML".into(),
16278                block_comment: Some(BlockCommentConfig {
16279                    start: "<!-- ".into(),
16280                    prefix: "".into(),
16281                    end: " -->".into(),
16282                    tab_size: 0,
16283                }),
16284                ..Default::default()
16285            },
16286            Some(tree_sitter_html::LANGUAGE.into()),
16287        )
16288        .with_injection_query(
16289            r#"
16290            (script_element
16291                (raw_text) @injection.content
16292                (#set! injection.language "javascript"))
16293            "#,
16294        )
16295        .unwrap(),
16296    );
16297
16298    let javascript_language = Arc::new(Language::new(
16299        LanguageConfig {
16300            name: "JavaScript".into(),
16301            line_comments: vec!["// ".into()],
16302            ..Default::default()
16303        },
16304        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16305    ));
16306
16307    cx.language_registry().add(html_language.clone());
16308    cx.language_registry().add(javascript_language);
16309    cx.update_buffer(|buffer, cx| {
16310        buffer.set_language(Some(html_language), cx);
16311    });
16312
16313    // Toggle comments for empty selections
16314    cx.set_state(
16315        &r#"
16316            <p>A</p>ˇ
16317            <p>B</p>ˇ
16318            <p>C</p>ˇ
16319        "#
16320        .unindent(),
16321    );
16322    cx.update_editor(|editor, window, cx| {
16323        editor.toggle_comments(&ToggleComments::default(), window, cx)
16324    });
16325    cx.assert_editor_state(
16326        &r#"
16327            <!-- <p>A</p>ˇ -->
16328            <!-- <p>B</p>ˇ -->
16329            <!-- <p>C</p>ˇ -->
16330        "#
16331        .unindent(),
16332    );
16333    cx.update_editor(|editor, window, cx| {
16334        editor.toggle_comments(&ToggleComments::default(), window, cx)
16335    });
16336    cx.assert_editor_state(
16337        &r#"
16338            <p>A</p>ˇ
16339            <p>B</p>ˇ
16340            <p>C</p>ˇ
16341        "#
16342        .unindent(),
16343    );
16344
16345    // Toggle comments for mixture of empty and non-empty selections, where
16346    // multiple selections occupy a given line.
16347    cx.set_state(
16348        &r#"
16349            <p>A«</p>
16350            <p>ˇ»B</p>ˇ
16351            <p>C«</p>
16352            <p>ˇ»D</p>ˇ
16353        "#
16354        .unindent(),
16355    );
16356
16357    cx.update_editor(|editor, window, cx| {
16358        editor.toggle_comments(&ToggleComments::default(), window, cx)
16359    });
16360    cx.assert_editor_state(
16361        &r#"
16362            <!-- <p>A«</p>
16363            <p>ˇ»B</p>ˇ -->
16364            <!-- <p>C«</p>
16365            <p>ˇ»D</p>ˇ -->
16366        "#
16367        .unindent(),
16368    );
16369    cx.update_editor(|editor, window, cx| {
16370        editor.toggle_comments(&ToggleComments::default(), window, cx)
16371    });
16372    cx.assert_editor_state(
16373        &r#"
16374            <p>A«</p>
16375            <p>ˇ»B</p>ˇ
16376            <p>C«</p>
16377            <p>ˇ»D</p>ˇ
16378        "#
16379        .unindent(),
16380    );
16381
16382    // Toggle comments when different languages are active for different
16383    // selections.
16384    cx.set_state(
16385        &r#"
16386            ˇ<script>
16387                ˇvar x = new Y();
16388            ˇ</script>
16389        "#
16390        .unindent(),
16391    );
16392    cx.executor().run_until_parked();
16393    cx.update_editor(|editor, window, cx| {
16394        editor.toggle_comments(&ToggleComments::default(), window, cx)
16395    });
16396    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16397    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16398    cx.assert_editor_state(
16399        &r#"
16400            <!-- ˇ<script> -->
16401                // ˇvar x = new Y();
16402            <!-- ˇ</script> -->
16403        "#
16404        .unindent(),
16405    );
16406}
16407
16408#[gpui::test]
16409fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16410    init_test(cx, |_| {});
16411
16412    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16413    let multibuffer = cx.new(|cx| {
16414        let mut multibuffer = MultiBuffer::new(ReadWrite);
16415        multibuffer.push_excerpts(
16416            buffer.clone(),
16417            [
16418                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16419                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16420            ],
16421            cx,
16422        );
16423        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16424        multibuffer
16425    });
16426
16427    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16428    editor.update_in(cx, |editor, window, cx| {
16429        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16430        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16431            s.select_ranges([
16432                Point::new(0, 0)..Point::new(0, 0),
16433                Point::new(1, 0)..Point::new(1, 0),
16434            ])
16435        });
16436
16437        editor.handle_input("X", window, cx);
16438        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16439        assert_eq!(
16440            editor.selections.ranges(&editor.display_snapshot(cx)),
16441            [
16442                Point::new(0, 1)..Point::new(0, 1),
16443                Point::new(1, 1)..Point::new(1, 1),
16444            ]
16445        );
16446
16447        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16448        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16449            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16450        });
16451        editor.backspace(&Default::default(), window, cx);
16452        assert_eq!(editor.text(cx), "Xa\nbbb");
16453        assert_eq!(
16454            editor.selections.ranges(&editor.display_snapshot(cx)),
16455            [Point::new(1, 0)..Point::new(1, 0)]
16456        );
16457
16458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16459            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16460        });
16461        editor.backspace(&Default::default(), window, cx);
16462        assert_eq!(editor.text(cx), "X\nbb");
16463        assert_eq!(
16464            editor.selections.ranges(&editor.display_snapshot(cx)),
16465            [Point::new(0, 1)..Point::new(0, 1)]
16466        );
16467    });
16468}
16469
16470#[gpui::test]
16471fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16472    init_test(cx, |_| {});
16473
16474    let markers = vec![('[', ']').into(), ('(', ')').into()];
16475    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16476        indoc! {"
16477            [aaaa
16478            (bbbb]
16479            cccc)",
16480        },
16481        markers.clone(),
16482    );
16483    let excerpt_ranges = markers.into_iter().map(|marker| {
16484        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16485        ExcerptRange::new(context)
16486    });
16487    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16488    let multibuffer = cx.new(|cx| {
16489        let mut multibuffer = MultiBuffer::new(ReadWrite);
16490        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16491        multibuffer
16492    });
16493
16494    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16495    editor.update_in(cx, |editor, window, cx| {
16496        let (expected_text, selection_ranges) = marked_text_ranges(
16497            indoc! {"
16498                aaaa
16499                bˇbbb
16500                bˇbbˇb
16501                cccc"
16502            },
16503            true,
16504        );
16505        assert_eq!(editor.text(cx), expected_text);
16506        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16507            s.select_ranges(
16508                selection_ranges
16509                    .iter()
16510                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
16511            )
16512        });
16513
16514        editor.handle_input("X", window, cx);
16515
16516        let (expected_text, expected_selections) = marked_text_ranges(
16517            indoc! {"
16518                aaaa
16519                bXˇbbXb
16520                bXˇbbXˇb
16521                cccc"
16522            },
16523            false,
16524        );
16525        assert_eq!(editor.text(cx), expected_text);
16526        assert_eq!(
16527            editor.selections.ranges(&editor.display_snapshot(cx)),
16528            expected_selections
16529                .iter()
16530                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16531                .collect::<Vec<_>>()
16532        );
16533
16534        editor.newline(&Newline, window, cx);
16535        let (expected_text, expected_selections) = marked_text_ranges(
16536            indoc! {"
16537                aaaa
16538                bX
16539                ˇbbX
16540                b
16541                bX
16542                ˇbbX
16543                ˇb
16544                cccc"
16545            },
16546            false,
16547        );
16548        assert_eq!(editor.text(cx), expected_text);
16549        assert_eq!(
16550            editor.selections.ranges(&editor.display_snapshot(cx)),
16551            expected_selections
16552                .iter()
16553                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16554                .collect::<Vec<_>>()
16555        );
16556    });
16557}
16558
16559#[gpui::test]
16560fn test_refresh_selections(cx: &mut TestAppContext) {
16561    init_test(cx, |_| {});
16562
16563    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16564    let mut excerpt1_id = None;
16565    let multibuffer = cx.new(|cx| {
16566        let mut multibuffer = MultiBuffer::new(ReadWrite);
16567        excerpt1_id = multibuffer
16568            .push_excerpts(
16569                buffer.clone(),
16570                [
16571                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16572                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16573                ],
16574                cx,
16575            )
16576            .into_iter()
16577            .next();
16578        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16579        multibuffer
16580    });
16581
16582    let editor = cx.add_window(|window, cx| {
16583        let mut editor = build_editor(multibuffer.clone(), window, cx);
16584        let snapshot = editor.snapshot(window, cx);
16585        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16586            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16587        });
16588        editor.begin_selection(
16589            Point::new(2, 1).to_display_point(&snapshot),
16590            true,
16591            1,
16592            window,
16593            cx,
16594        );
16595        assert_eq!(
16596            editor.selections.ranges(&editor.display_snapshot(cx)),
16597            [
16598                Point::new(1, 3)..Point::new(1, 3),
16599                Point::new(2, 1)..Point::new(2, 1),
16600            ]
16601        );
16602        editor
16603    });
16604
16605    // Refreshing selections is a no-op when excerpts haven't changed.
16606    _ = editor.update(cx, |editor, window, cx| {
16607        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16608        assert_eq!(
16609            editor.selections.ranges(&editor.display_snapshot(cx)),
16610            [
16611                Point::new(1, 3)..Point::new(1, 3),
16612                Point::new(2, 1)..Point::new(2, 1),
16613            ]
16614        );
16615    });
16616
16617    multibuffer.update(cx, |multibuffer, cx| {
16618        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16619    });
16620    _ = editor.update(cx, |editor, window, cx| {
16621        // Removing an excerpt causes the first selection to become degenerate.
16622        assert_eq!(
16623            editor.selections.ranges(&editor.display_snapshot(cx)),
16624            [
16625                Point::new(0, 0)..Point::new(0, 0),
16626                Point::new(0, 1)..Point::new(0, 1)
16627            ]
16628        );
16629
16630        // Refreshing selections will relocate the first selection to the original buffer
16631        // location.
16632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16633        assert_eq!(
16634            editor.selections.ranges(&editor.display_snapshot(cx)),
16635            [
16636                Point::new(0, 1)..Point::new(0, 1),
16637                Point::new(0, 3)..Point::new(0, 3)
16638            ]
16639        );
16640        assert!(editor.selections.pending_anchor().is_some());
16641    });
16642}
16643
16644#[gpui::test]
16645fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16646    init_test(cx, |_| {});
16647
16648    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16649    let mut excerpt1_id = None;
16650    let multibuffer = cx.new(|cx| {
16651        let mut multibuffer = MultiBuffer::new(ReadWrite);
16652        excerpt1_id = multibuffer
16653            .push_excerpts(
16654                buffer.clone(),
16655                [
16656                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16657                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16658                ],
16659                cx,
16660            )
16661            .into_iter()
16662            .next();
16663        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16664        multibuffer
16665    });
16666
16667    let editor = cx.add_window(|window, cx| {
16668        let mut editor = build_editor(multibuffer.clone(), window, cx);
16669        let snapshot = editor.snapshot(window, cx);
16670        editor.begin_selection(
16671            Point::new(1, 3).to_display_point(&snapshot),
16672            false,
16673            1,
16674            window,
16675            cx,
16676        );
16677        assert_eq!(
16678            editor.selections.ranges(&editor.display_snapshot(cx)),
16679            [Point::new(1, 3)..Point::new(1, 3)]
16680        );
16681        editor
16682    });
16683
16684    multibuffer.update(cx, |multibuffer, cx| {
16685        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16686    });
16687    _ = editor.update(cx, |editor, window, cx| {
16688        assert_eq!(
16689            editor.selections.ranges(&editor.display_snapshot(cx)),
16690            [Point::new(0, 0)..Point::new(0, 0)]
16691        );
16692
16693        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16694        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16695        assert_eq!(
16696            editor.selections.ranges(&editor.display_snapshot(cx)),
16697            [Point::new(0, 3)..Point::new(0, 3)]
16698        );
16699        assert!(editor.selections.pending_anchor().is_some());
16700    });
16701}
16702
16703#[gpui::test]
16704async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16705    init_test(cx, |_| {});
16706
16707    let language = Arc::new(
16708        Language::new(
16709            LanguageConfig {
16710                brackets: BracketPairConfig {
16711                    pairs: vec![
16712                        BracketPair {
16713                            start: "{".to_string(),
16714                            end: "}".to_string(),
16715                            close: true,
16716                            surround: true,
16717                            newline: true,
16718                        },
16719                        BracketPair {
16720                            start: "/* ".to_string(),
16721                            end: " */".to_string(),
16722                            close: true,
16723                            surround: true,
16724                            newline: true,
16725                        },
16726                    ],
16727                    ..Default::default()
16728                },
16729                ..Default::default()
16730            },
16731            Some(tree_sitter_rust::LANGUAGE.into()),
16732        )
16733        .with_indents_query("")
16734        .unwrap(),
16735    );
16736
16737    let text = concat!(
16738        "{   }\n",     //
16739        "  x\n",       //
16740        "  /*   */\n", //
16741        "x\n",         //
16742        "{{} }\n",     //
16743    );
16744
16745    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16746    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16747    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16748    editor
16749        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16750        .await;
16751
16752    editor.update_in(cx, |editor, window, cx| {
16753        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16754            s.select_display_ranges([
16755                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16756                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16757                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16758            ])
16759        });
16760        editor.newline(&Newline, window, cx);
16761
16762        assert_eq!(
16763            editor.buffer().read(cx).read(cx).text(),
16764            concat!(
16765                "{ \n",    // Suppress rustfmt
16766                "\n",      //
16767                "}\n",     //
16768                "  x\n",   //
16769                "  /* \n", //
16770                "  \n",    //
16771                "  */\n",  //
16772                "x\n",     //
16773                "{{} \n",  //
16774                "}\n",     //
16775            )
16776        );
16777    });
16778}
16779
16780#[gpui::test]
16781fn test_highlighted_ranges(cx: &mut TestAppContext) {
16782    init_test(cx, |_| {});
16783
16784    let editor = cx.add_window(|window, cx| {
16785        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16786        build_editor(buffer, window, cx)
16787    });
16788
16789    _ = editor.update(cx, |editor, window, cx| {
16790        struct Type1;
16791        struct Type2;
16792
16793        let buffer = editor.buffer.read(cx).snapshot(cx);
16794
16795        let anchor_range =
16796            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16797
16798        editor.highlight_background::<Type1>(
16799            &[
16800                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16801                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16802                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16803                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16804            ],
16805            |_| Hsla::red(),
16806            cx,
16807        );
16808        editor.highlight_background::<Type2>(
16809            &[
16810                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16811                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16812                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16813                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16814            ],
16815            |_| Hsla::green(),
16816            cx,
16817        );
16818
16819        let snapshot = editor.snapshot(window, cx);
16820        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16821            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16822            &snapshot,
16823            cx.theme(),
16824        );
16825        assert_eq!(
16826            highlighted_ranges,
16827            &[
16828                (
16829                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16830                    Hsla::green(),
16831                ),
16832                (
16833                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16834                    Hsla::red(),
16835                ),
16836                (
16837                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16838                    Hsla::green(),
16839                ),
16840                (
16841                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16842                    Hsla::red(),
16843                ),
16844            ]
16845        );
16846        assert_eq!(
16847            editor.sorted_background_highlights_in_range(
16848                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16849                &snapshot,
16850                cx.theme(),
16851            ),
16852            &[(
16853                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16854                Hsla::red(),
16855            )]
16856        );
16857    });
16858}
16859
16860#[gpui::test]
16861async fn test_following(cx: &mut TestAppContext) {
16862    init_test(cx, |_| {});
16863
16864    let fs = FakeFs::new(cx.executor());
16865    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16866
16867    let buffer = project.update(cx, |project, cx| {
16868        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16869        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16870    });
16871    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16872    let follower = cx.update(|cx| {
16873        cx.open_window(
16874            WindowOptions {
16875                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16876                    gpui::Point::new(px(0.), px(0.)),
16877                    gpui::Point::new(px(10.), px(80.)),
16878                ))),
16879                ..Default::default()
16880            },
16881            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16882        )
16883        .unwrap()
16884    });
16885
16886    let is_still_following = Rc::new(RefCell::new(true));
16887    let follower_edit_event_count = Rc::new(RefCell::new(0));
16888    let pending_update = Rc::new(RefCell::new(None));
16889    let leader_entity = leader.root(cx).unwrap();
16890    let follower_entity = follower.root(cx).unwrap();
16891    _ = follower.update(cx, {
16892        let update = pending_update.clone();
16893        let is_still_following = is_still_following.clone();
16894        let follower_edit_event_count = follower_edit_event_count.clone();
16895        |_, window, cx| {
16896            cx.subscribe_in(
16897                &leader_entity,
16898                window,
16899                move |_, leader, event, window, cx| {
16900                    leader.read(cx).add_event_to_update_proto(
16901                        event,
16902                        &mut update.borrow_mut(),
16903                        window,
16904                        cx,
16905                    );
16906                },
16907            )
16908            .detach();
16909
16910            cx.subscribe_in(
16911                &follower_entity,
16912                window,
16913                move |_, _, event: &EditorEvent, _window, _cx| {
16914                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16915                        *is_still_following.borrow_mut() = false;
16916                    }
16917
16918                    if let EditorEvent::BufferEdited = event {
16919                        *follower_edit_event_count.borrow_mut() += 1;
16920                    }
16921                },
16922            )
16923            .detach();
16924        }
16925    });
16926
16927    // Update the selections only
16928    _ = leader.update(cx, |leader, window, cx| {
16929        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16930            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
16931        });
16932    });
16933    follower
16934        .update(cx, |follower, window, cx| {
16935            follower.apply_update_proto(
16936                &project,
16937                pending_update.borrow_mut().take().unwrap(),
16938                window,
16939                cx,
16940            )
16941        })
16942        .unwrap()
16943        .await
16944        .unwrap();
16945    _ = follower.update(cx, |follower, _, cx| {
16946        assert_eq!(
16947            follower.selections.ranges(&follower.display_snapshot(cx)),
16948            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
16949        );
16950    });
16951    assert!(*is_still_following.borrow());
16952    assert_eq!(*follower_edit_event_count.borrow(), 0);
16953
16954    // Update the scroll position only
16955    _ = leader.update(cx, |leader, window, cx| {
16956        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16957    });
16958    follower
16959        .update(cx, |follower, window, cx| {
16960            follower.apply_update_proto(
16961                &project,
16962                pending_update.borrow_mut().take().unwrap(),
16963                window,
16964                cx,
16965            )
16966        })
16967        .unwrap()
16968        .await
16969        .unwrap();
16970    assert_eq!(
16971        follower
16972            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16973            .unwrap(),
16974        gpui::Point::new(1.5, 3.5)
16975    );
16976    assert!(*is_still_following.borrow());
16977    assert_eq!(*follower_edit_event_count.borrow(), 0);
16978
16979    // Update the selections and scroll position. The follower's scroll position is updated
16980    // via autoscroll, not via the leader's exact scroll position.
16981    _ = leader.update(cx, |leader, window, cx| {
16982        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16983            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
16984        });
16985        leader.request_autoscroll(Autoscroll::newest(), cx);
16986        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16987    });
16988    follower
16989        .update(cx, |follower, window, cx| {
16990            follower.apply_update_proto(
16991                &project,
16992                pending_update.borrow_mut().take().unwrap(),
16993                window,
16994                cx,
16995            )
16996        })
16997        .unwrap()
16998        .await
16999        .unwrap();
17000    _ = follower.update(cx, |follower, _, cx| {
17001        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17002        assert_eq!(
17003            follower.selections.ranges(&follower.display_snapshot(cx)),
17004            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17005        );
17006    });
17007    assert!(*is_still_following.borrow());
17008
17009    // Creating a pending selection that precedes another selection
17010    _ = leader.update(cx, |leader, window, cx| {
17011        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17012            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17013        });
17014        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17015    });
17016    follower
17017        .update(cx, |follower, window, cx| {
17018            follower.apply_update_proto(
17019                &project,
17020                pending_update.borrow_mut().take().unwrap(),
17021                window,
17022                cx,
17023            )
17024        })
17025        .unwrap()
17026        .await
17027        .unwrap();
17028    _ = follower.update(cx, |follower, _, cx| {
17029        assert_eq!(
17030            follower.selections.ranges(&follower.display_snapshot(cx)),
17031            vec![
17032                MultiBufferOffset(0)..MultiBufferOffset(0),
17033                MultiBufferOffset(1)..MultiBufferOffset(1)
17034            ]
17035        );
17036    });
17037    assert!(*is_still_following.borrow());
17038
17039    // Extend the pending selection so that it surrounds another selection
17040    _ = leader.update(cx, |leader, window, cx| {
17041        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17042    });
17043    follower
17044        .update(cx, |follower, window, cx| {
17045            follower.apply_update_proto(
17046                &project,
17047                pending_update.borrow_mut().take().unwrap(),
17048                window,
17049                cx,
17050            )
17051        })
17052        .unwrap()
17053        .await
17054        .unwrap();
17055    _ = follower.update(cx, |follower, _, cx| {
17056        assert_eq!(
17057            follower.selections.ranges(&follower.display_snapshot(cx)),
17058            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17059        );
17060    });
17061
17062    // Scrolling locally breaks the follow
17063    _ = follower.update(cx, |follower, window, cx| {
17064        let top_anchor = follower
17065            .buffer()
17066            .read(cx)
17067            .read(cx)
17068            .anchor_after(MultiBufferOffset(0));
17069        follower.set_scroll_anchor(
17070            ScrollAnchor {
17071                anchor: top_anchor,
17072                offset: gpui::Point::new(0.0, 0.5),
17073            },
17074            window,
17075            cx,
17076        );
17077    });
17078    assert!(!(*is_still_following.borrow()));
17079}
17080
17081#[gpui::test]
17082async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17083    init_test(cx, |_| {});
17084
17085    let fs = FakeFs::new(cx.executor());
17086    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17087    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17088    let pane = workspace
17089        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17090        .unwrap();
17091
17092    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17093
17094    let leader = pane.update_in(cx, |_, window, cx| {
17095        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17096        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17097    });
17098
17099    // Start following the editor when it has no excerpts.
17100    let mut state_message =
17101        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17102    let workspace_entity = workspace.root(cx).unwrap();
17103    let follower_1 = cx
17104        .update_window(*workspace.deref(), |_, window, cx| {
17105            Editor::from_state_proto(
17106                workspace_entity,
17107                ViewId {
17108                    creator: CollaboratorId::PeerId(PeerId::default()),
17109                    id: 0,
17110                },
17111                &mut state_message,
17112                window,
17113                cx,
17114            )
17115        })
17116        .unwrap()
17117        .unwrap()
17118        .await
17119        .unwrap();
17120
17121    let update_message = Rc::new(RefCell::new(None));
17122    follower_1.update_in(cx, {
17123        let update = update_message.clone();
17124        |_, window, cx| {
17125            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17126                leader.read(cx).add_event_to_update_proto(
17127                    event,
17128                    &mut update.borrow_mut(),
17129                    window,
17130                    cx,
17131                );
17132            })
17133            .detach();
17134        }
17135    });
17136
17137    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17138        (
17139            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17140            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17141        )
17142    });
17143
17144    // Insert some excerpts.
17145    leader.update(cx, |leader, cx| {
17146        leader.buffer.update(cx, |multibuffer, cx| {
17147            multibuffer.set_excerpts_for_path(
17148                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17149                buffer_1.clone(),
17150                vec![
17151                    Point::row_range(0..3),
17152                    Point::row_range(1..6),
17153                    Point::row_range(12..15),
17154                ],
17155                0,
17156                cx,
17157            );
17158            multibuffer.set_excerpts_for_path(
17159                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17160                buffer_2.clone(),
17161                vec![Point::row_range(0..6), Point::row_range(8..12)],
17162                0,
17163                cx,
17164            );
17165        });
17166    });
17167
17168    // Apply the update of adding the excerpts.
17169    follower_1
17170        .update_in(cx, |follower, window, cx| {
17171            follower.apply_update_proto(
17172                &project,
17173                update_message.borrow().clone().unwrap(),
17174                window,
17175                cx,
17176            )
17177        })
17178        .await
17179        .unwrap();
17180    assert_eq!(
17181        follower_1.update(cx, |editor, cx| editor.text(cx)),
17182        leader.update(cx, |editor, cx| editor.text(cx))
17183    );
17184    update_message.borrow_mut().take();
17185
17186    // Start following separately after it already has excerpts.
17187    let mut state_message =
17188        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17189    let workspace_entity = workspace.root(cx).unwrap();
17190    let follower_2 = cx
17191        .update_window(*workspace.deref(), |_, window, cx| {
17192            Editor::from_state_proto(
17193                workspace_entity,
17194                ViewId {
17195                    creator: CollaboratorId::PeerId(PeerId::default()),
17196                    id: 0,
17197                },
17198                &mut state_message,
17199                window,
17200                cx,
17201            )
17202        })
17203        .unwrap()
17204        .unwrap()
17205        .await
17206        .unwrap();
17207    assert_eq!(
17208        follower_2.update(cx, |editor, cx| editor.text(cx)),
17209        leader.update(cx, |editor, cx| editor.text(cx))
17210    );
17211
17212    // Remove some excerpts.
17213    leader.update(cx, |leader, cx| {
17214        leader.buffer.update(cx, |multibuffer, cx| {
17215            let excerpt_ids = multibuffer.excerpt_ids();
17216            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17217            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17218        });
17219    });
17220
17221    // Apply the update of removing the excerpts.
17222    follower_1
17223        .update_in(cx, |follower, window, cx| {
17224            follower.apply_update_proto(
17225                &project,
17226                update_message.borrow().clone().unwrap(),
17227                window,
17228                cx,
17229            )
17230        })
17231        .await
17232        .unwrap();
17233    follower_2
17234        .update_in(cx, |follower, window, cx| {
17235            follower.apply_update_proto(
17236                &project,
17237                update_message.borrow().clone().unwrap(),
17238                window,
17239                cx,
17240            )
17241        })
17242        .await
17243        .unwrap();
17244    update_message.borrow_mut().take();
17245    assert_eq!(
17246        follower_1.update(cx, |editor, cx| editor.text(cx)),
17247        leader.update(cx, |editor, cx| editor.text(cx))
17248    );
17249}
17250
17251#[gpui::test]
17252async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17253    init_test(cx, |_| {});
17254
17255    let mut cx = EditorTestContext::new(cx).await;
17256    let lsp_store =
17257        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17258
17259    cx.set_state(indoc! {"
17260        ˇfn func(abc def: i32) -> u32 {
17261        }
17262    "});
17263
17264    cx.update(|_, cx| {
17265        lsp_store.update(cx, |lsp_store, cx| {
17266            lsp_store
17267                .update_diagnostics(
17268                    LanguageServerId(0),
17269                    lsp::PublishDiagnosticsParams {
17270                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17271                        version: None,
17272                        diagnostics: vec![
17273                            lsp::Diagnostic {
17274                                range: lsp::Range::new(
17275                                    lsp::Position::new(0, 11),
17276                                    lsp::Position::new(0, 12),
17277                                ),
17278                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17279                                ..Default::default()
17280                            },
17281                            lsp::Diagnostic {
17282                                range: lsp::Range::new(
17283                                    lsp::Position::new(0, 12),
17284                                    lsp::Position::new(0, 15),
17285                                ),
17286                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17287                                ..Default::default()
17288                            },
17289                            lsp::Diagnostic {
17290                                range: lsp::Range::new(
17291                                    lsp::Position::new(0, 25),
17292                                    lsp::Position::new(0, 28),
17293                                ),
17294                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17295                                ..Default::default()
17296                            },
17297                        ],
17298                    },
17299                    None,
17300                    DiagnosticSourceKind::Pushed,
17301                    &[],
17302                    cx,
17303                )
17304                .unwrap()
17305        });
17306    });
17307
17308    executor.run_until_parked();
17309
17310    cx.update_editor(|editor, window, cx| {
17311        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17312    });
17313
17314    cx.assert_editor_state(indoc! {"
17315        fn func(abc def: i32) -> ˇu32 {
17316        }
17317    "});
17318
17319    cx.update_editor(|editor, window, cx| {
17320        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17321    });
17322
17323    cx.assert_editor_state(indoc! {"
17324        fn func(abc ˇdef: i32) -> u32 {
17325        }
17326    "});
17327
17328    cx.update_editor(|editor, window, cx| {
17329        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17330    });
17331
17332    cx.assert_editor_state(indoc! {"
17333        fn func(abcˇ def: i32) -> u32 {
17334        }
17335    "});
17336
17337    cx.update_editor(|editor, window, cx| {
17338        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17339    });
17340
17341    cx.assert_editor_state(indoc! {"
17342        fn func(abc def: i32) -> ˇu32 {
17343        }
17344    "});
17345}
17346
17347#[gpui::test]
17348async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17349    init_test(cx, |_| {});
17350
17351    let mut cx = EditorTestContext::new(cx).await;
17352
17353    let diff_base = r#"
17354        use some::mod;
17355
17356        const A: u32 = 42;
17357
17358        fn main() {
17359            println!("hello");
17360
17361            println!("world");
17362        }
17363        "#
17364    .unindent();
17365
17366    // Edits are modified, removed, modified, added
17367    cx.set_state(
17368        &r#"
17369        use some::modified;
17370
17371        ˇ
17372        fn main() {
17373            println!("hello there");
17374
17375            println!("around the");
17376            println!("world");
17377        }
17378        "#
17379        .unindent(),
17380    );
17381
17382    cx.set_head_text(&diff_base);
17383    executor.run_until_parked();
17384
17385    cx.update_editor(|editor, window, cx| {
17386        //Wrap around the bottom of the buffer
17387        for _ in 0..3 {
17388            editor.go_to_next_hunk(&GoToHunk, window, cx);
17389        }
17390    });
17391
17392    cx.assert_editor_state(
17393        &r#"
17394        ˇuse some::modified;
17395
17396
17397        fn main() {
17398            println!("hello there");
17399
17400            println!("around the");
17401            println!("world");
17402        }
17403        "#
17404        .unindent(),
17405    );
17406
17407    cx.update_editor(|editor, window, cx| {
17408        //Wrap around the top of the buffer
17409        for _ in 0..2 {
17410            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17411        }
17412    });
17413
17414    cx.assert_editor_state(
17415        &r#"
17416        use some::modified;
17417
17418
17419        fn main() {
17420        ˇ    println!("hello there");
17421
17422            println!("around the");
17423            println!("world");
17424        }
17425        "#
17426        .unindent(),
17427    );
17428
17429    cx.update_editor(|editor, window, cx| {
17430        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17431    });
17432
17433    cx.assert_editor_state(
17434        &r#"
17435        use some::modified;
17436
17437        ˇ
17438        fn main() {
17439            println!("hello there");
17440
17441            println!("around the");
17442            println!("world");
17443        }
17444        "#
17445        .unindent(),
17446    );
17447
17448    cx.update_editor(|editor, window, cx| {
17449        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17450    });
17451
17452    cx.assert_editor_state(
17453        &r#"
17454        ˇuse some::modified;
17455
17456
17457        fn main() {
17458            println!("hello there");
17459
17460            println!("around the");
17461            println!("world");
17462        }
17463        "#
17464        .unindent(),
17465    );
17466
17467    cx.update_editor(|editor, window, cx| {
17468        for _ in 0..2 {
17469            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17470        }
17471    });
17472
17473    cx.assert_editor_state(
17474        &r#"
17475        use some::modified;
17476
17477
17478        fn main() {
17479        ˇ    println!("hello there");
17480
17481            println!("around the");
17482            println!("world");
17483        }
17484        "#
17485        .unindent(),
17486    );
17487
17488    cx.update_editor(|editor, window, cx| {
17489        editor.fold(&Fold, window, cx);
17490    });
17491
17492    cx.update_editor(|editor, window, cx| {
17493        editor.go_to_next_hunk(&GoToHunk, window, cx);
17494    });
17495
17496    cx.assert_editor_state(
17497        &r#"
17498        ˇuse some::modified;
17499
17500
17501        fn main() {
17502            println!("hello there");
17503
17504            println!("around the");
17505            println!("world");
17506        }
17507        "#
17508        .unindent(),
17509    );
17510}
17511
17512#[test]
17513fn test_split_words() {
17514    fn split(text: &str) -> Vec<&str> {
17515        split_words(text).collect()
17516    }
17517
17518    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17519    assert_eq!(split("hello_world"), &["hello_", "world"]);
17520    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17521    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17522    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17523    assert_eq!(split("helloworld"), &["helloworld"]);
17524
17525    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17526}
17527
17528#[test]
17529fn test_split_words_for_snippet_prefix() {
17530    fn split(text: &str) -> Vec<&str> {
17531        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
17532    }
17533
17534    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17535    assert_eq!(split("hello_world"), &["hello_world"]);
17536    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17537    assert_eq!(split("Hello_World"), &["Hello_World"]);
17538    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17539    assert_eq!(split("helloworld"), &["helloworld"]);
17540    assert_eq!(
17541        split("this@is!@#$^many   . symbols"),
17542        &[
17543            "symbols",
17544            " symbols",
17545            ". symbols",
17546            " . symbols",
17547            "  . symbols",
17548            "   . symbols",
17549            "many   . symbols",
17550            "^many   . symbols",
17551            "$^many   . symbols",
17552            "#$^many   . symbols",
17553            "@#$^many   . symbols",
17554            "!@#$^many   . symbols",
17555            "is!@#$^many   . symbols",
17556            "@is!@#$^many   . symbols",
17557            "this@is!@#$^many   . symbols",
17558        ],
17559    );
17560    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
17561}
17562
17563#[gpui::test]
17564async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17565    init_test(cx, |_| {});
17566
17567    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17568
17569    #[track_caller]
17570    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
17571        let _state_context = cx.set_state(before);
17572        cx.run_until_parked();
17573        cx.update_editor(|editor, window, cx| {
17574            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17575        });
17576        cx.run_until_parked();
17577        cx.assert_editor_state(after);
17578    }
17579
17580    // Outside bracket jumps to outside of matching bracket
17581    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
17582    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
17583
17584    // Inside bracket jumps to inside of matching bracket
17585    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
17586    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
17587
17588    // When outside a bracket and inside, favor jumping to the inside bracket
17589    assert(
17590        "console.log('foo', [1, 2, 3]ˇ);",
17591        "console.log('foo', ˇ[1, 2, 3]);",
17592        &mut cx,
17593    );
17594    assert(
17595        "console.log(ˇ'foo', [1, 2, 3]);",
17596        "console.log('foo'ˇ, [1, 2, 3]);",
17597        &mut cx,
17598    );
17599
17600    // Bias forward if two options are equally likely
17601    assert(
17602        "let result = curried_fun()ˇ();",
17603        "let result = curried_fun()()ˇ;",
17604        &mut cx,
17605    );
17606
17607    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17608    assert(
17609        indoc! {"
17610            function test() {
17611                console.log('test')ˇ
17612            }"},
17613        indoc! {"
17614            function test() {
17615                console.logˇ('test')
17616            }"},
17617        &mut cx,
17618    );
17619}
17620
17621#[gpui::test]
17622async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
17623    init_test(cx, |_| {});
17624    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
17625    language_registry.add(markdown_lang());
17626    language_registry.add(rust_lang());
17627    let buffer = cx.new(|cx| {
17628        let mut buffer = language::Buffer::local(
17629            indoc! {"
17630            ```rs
17631            impl Worktree {
17632                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17633                }
17634            }
17635            ```
17636        "},
17637            cx,
17638        );
17639        buffer.set_language_registry(language_registry.clone());
17640        buffer.set_language(Some(markdown_lang()), cx);
17641        buffer
17642    });
17643    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17644    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17645    cx.executor().run_until_parked();
17646    _ = editor.update(cx, |editor, window, cx| {
17647        // Case 1: Test outer enclosing brackets
17648        select_ranges(
17649            editor,
17650            &indoc! {"
17651                ```rs
17652                impl Worktree {
17653                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17654                    }
1765517656                ```
17657            "},
17658            window,
17659            cx,
17660        );
17661        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17662        assert_text_with_selections(
17663            editor,
17664            &indoc! {"
17665                ```rs
17666                impl Worktree ˇ{
17667                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17668                    }
17669                }
17670                ```
17671            "},
17672            cx,
17673        );
17674        // Case 2: Test inner enclosing brackets
17675        select_ranges(
17676            editor,
17677            &indoc! {"
17678                ```rs
17679                impl Worktree {
17680                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1768117682                }
17683                ```
17684            "},
17685            window,
17686            cx,
17687        );
17688        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17689        assert_text_with_selections(
17690            editor,
17691            &indoc! {"
17692                ```rs
17693                impl Worktree {
17694                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
17695                    }
17696                }
17697                ```
17698            "},
17699            cx,
17700        );
17701    });
17702}
17703
17704#[gpui::test]
17705async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17706    init_test(cx, |_| {});
17707
17708    let fs = FakeFs::new(cx.executor());
17709    fs.insert_tree(
17710        path!("/a"),
17711        json!({
17712            "main.rs": "fn main() { let a = 5; }",
17713            "other.rs": "// Test file",
17714        }),
17715    )
17716    .await;
17717    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17718
17719    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17720    language_registry.add(Arc::new(Language::new(
17721        LanguageConfig {
17722            name: "Rust".into(),
17723            matcher: LanguageMatcher {
17724                path_suffixes: vec!["rs".to_string()],
17725                ..Default::default()
17726            },
17727            brackets: BracketPairConfig {
17728                pairs: vec![BracketPair {
17729                    start: "{".to_string(),
17730                    end: "}".to_string(),
17731                    close: true,
17732                    surround: true,
17733                    newline: true,
17734                }],
17735                disabled_scopes_by_bracket_ix: Vec::new(),
17736            },
17737            ..Default::default()
17738        },
17739        Some(tree_sitter_rust::LANGUAGE.into()),
17740    )));
17741    let mut fake_servers = language_registry.register_fake_lsp(
17742        "Rust",
17743        FakeLspAdapter {
17744            capabilities: lsp::ServerCapabilities {
17745                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17746                    first_trigger_character: "{".to_string(),
17747                    more_trigger_character: None,
17748                }),
17749                ..Default::default()
17750            },
17751            ..Default::default()
17752        },
17753    );
17754
17755    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17756
17757    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17758
17759    let worktree_id = workspace
17760        .update(cx, |workspace, _, cx| {
17761            workspace.project().update(cx, |project, cx| {
17762                project.worktrees(cx).next().unwrap().read(cx).id()
17763            })
17764        })
17765        .unwrap();
17766
17767    let buffer = project
17768        .update(cx, |project, cx| {
17769            project.open_local_buffer(path!("/a/main.rs"), cx)
17770        })
17771        .await
17772        .unwrap();
17773    let editor_handle = workspace
17774        .update(cx, |workspace, window, cx| {
17775            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17776        })
17777        .unwrap()
17778        .await
17779        .unwrap()
17780        .downcast::<Editor>()
17781        .unwrap();
17782
17783    cx.executor().start_waiting();
17784    let fake_server = fake_servers.next().await.unwrap();
17785
17786    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17787        |params, _| async move {
17788            assert_eq!(
17789                params.text_document_position.text_document.uri,
17790                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17791            );
17792            assert_eq!(
17793                params.text_document_position.position,
17794                lsp::Position::new(0, 21),
17795            );
17796
17797            Ok(Some(vec![lsp::TextEdit {
17798                new_text: "]".to_string(),
17799                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17800            }]))
17801        },
17802    );
17803
17804    editor_handle.update_in(cx, |editor, window, cx| {
17805        window.focus(&editor.focus_handle(cx));
17806        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17807            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17808        });
17809        editor.handle_input("{", window, cx);
17810    });
17811
17812    cx.executor().run_until_parked();
17813
17814    buffer.update(cx, |buffer, _| {
17815        assert_eq!(
17816            buffer.text(),
17817            "fn main() { let a = {5}; }",
17818            "No extra braces from on type formatting should appear in the buffer"
17819        )
17820    });
17821}
17822
17823#[gpui::test(iterations = 20, seeds(31))]
17824async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17825    init_test(cx, |_| {});
17826
17827    let mut cx = EditorLspTestContext::new_rust(
17828        lsp::ServerCapabilities {
17829            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17830                first_trigger_character: ".".to_string(),
17831                more_trigger_character: None,
17832            }),
17833            ..Default::default()
17834        },
17835        cx,
17836    )
17837    .await;
17838
17839    cx.update_buffer(|buffer, _| {
17840        // This causes autoindent to be async.
17841        buffer.set_sync_parse_timeout(Duration::ZERO)
17842    });
17843
17844    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17845    cx.simulate_keystroke("\n");
17846    cx.run_until_parked();
17847
17848    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17849    let mut request =
17850        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17851            let buffer_cloned = buffer_cloned.clone();
17852            async move {
17853                buffer_cloned.update(&mut cx, |buffer, _| {
17854                    assert_eq!(
17855                        buffer.text(),
17856                        "fn c() {\n    d()\n        .\n}\n",
17857                        "OnTypeFormatting should triggered after autoindent applied"
17858                    )
17859                })?;
17860
17861                Ok(Some(vec![]))
17862            }
17863        });
17864
17865    cx.simulate_keystroke(".");
17866    cx.run_until_parked();
17867
17868    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17869    assert!(request.next().await.is_some());
17870    request.close();
17871    assert!(request.next().await.is_none());
17872}
17873
17874#[gpui::test]
17875async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17876    init_test(cx, |_| {});
17877
17878    let fs = FakeFs::new(cx.executor());
17879    fs.insert_tree(
17880        path!("/a"),
17881        json!({
17882            "main.rs": "fn main() { let a = 5; }",
17883            "other.rs": "// Test file",
17884        }),
17885    )
17886    .await;
17887
17888    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17889
17890    let server_restarts = Arc::new(AtomicUsize::new(0));
17891    let closure_restarts = Arc::clone(&server_restarts);
17892    let language_server_name = "test language server";
17893    let language_name: LanguageName = "Rust".into();
17894
17895    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17896    language_registry.add(Arc::new(Language::new(
17897        LanguageConfig {
17898            name: language_name.clone(),
17899            matcher: LanguageMatcher {
17900                path_suffixes: vec!["rs".to_string()],
17901                ..Default::default()
17902            },
17903            ..Default::default()
17904        },
17905        Some(tree_sitter_rust::LANGUAGE.into()),
17906    )));
17907    let mut fake_servers = language_registry.register_fake_lsp(
17908        "Rust",
17909        FakeLspAdapter {
17910            name: language_server_name,
17911            initialization_options: Some(json!({
17912                "testOptionValue": true
17913            })),
17914            initializer: Some(Box::new(move |fake_server| {
17915                let task_restarts = Arc::clone(&closure_restarts);
17916                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17917                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17918                    futures::future::ready(Ok(()))
17919                });
17920            })),
17921            ..Default::default()
17922        },
17923    );
17924
17925    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17926    let _buffer = project
17927        .update(cx, |project, cx| {
17928            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17929        })
17930        .await
17931        .unwrap();
17932    let _fake_server = fake_servers.next().await.unwrap();
17933    update_test_language_settings(cx, |language_settings| {
17934        language_settings.languages.0.insert(
17935            language_name.clone().0,
17936            LanguageSettingsContent {
17937                tab_size: NonZeroU32::new(8),
17938                ..Default::default()
17939            },
17940        );
17941    });
17942    cx.executor().run_until_parked();
17943    assert_eq!(
17944        server_restarts.load(atomic::Ordering::Acquire),
17945        0,
17946        "Should not restart LSP server on an unrelated change"
17947    );
17948
17949    update_test_project_settings(cx, |project_settings| {
17950        project_settings.lsp.insert(
17951            "Some other server name".into(),
17952            LspSettings {
17953                binary: None,
17954                settings: None,
17955                initialization_options: Some(json!({
17956                    "some other init value": false
17957                })),
17958                enable_lsp_tasks: false,
17959                fetch: None,
17960            },
17961        );
17962    });
17963    cx.executor().run_until_parked();
17964    assert_eq!(
17965        server_restarts.load(atomic::Ordering::Acquire),
17966        0,
17967        "Should not restart LSP server on an unrelated LSP settings change"
17968    );
17969
17970    update_test_project_settings(cx, |project_settings| {
17971        project_settings.lsp.insert(
17972            language_server_name.into(),
17973            LspSettings {
17974                binary: None,
17975                settings: None,
17976                initialization_options: Some(json!({
17977                    "anotherInitValue": false
17978                })),
17979                enable_lsp_tasks: false,
17980                fetch: None,
17981            },
17982        );
17983    });
17984    cx.executor().run_until_parked();
17985    assert_eq!(
17986        server_restarts.load(atomic::Ordering::Acquire),
17987        1,
17988        "Should restart LSP server on a related LSP settings change"
17989    );
17990
17991    update_test_project_settings(cx, |project_settings| {
17992        project_settings.lsp.insert(
17993            language_server_name.into(),
17994            LspSettings {
17995                binary: None,
17996                settings: None,
17997                initialization_options: Some(json!({
17998                    "anotherInitValue": false
17999                })),
18000                enable_lsp_tasks: false,
18001                fetch: None,
18002            },
18003        );
18004    });
18005    cx.executor().run_until_parked();
18006    assert_eq!(
18007        server_restarts.load(atomic::Ordering::Acquire),
18008        1,
18009        "Should not restart LSP server on a related LSP settings change that is the same"
18010    );
18011
18012    update_test_project_settings(cx, |project_settings| {
18013        project_settings.lsp.insert(
18014            language_server_name.into(),
18015            LspSettings {
18016                binary: None,
18017                settings: None,
18018                initialization_options: None,
18019                enable_lsp_tasks: false,
18020                fetch: None,
18021            },
18022        );
18023    });
18024    cx.executor().run_until_parked();
18025    assert_eq!(
18026        server_restarts.load(atomic::Ordering::Acquire),
18027        2,
18028        "Should restart LSP server on another related LSP settings change"
18029    );
18030}
18031
18032#[gpui::test]
18033async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18034    init_test(cx, |_| {});
18035
18036    let mut cx = EditorLspTestContext::new_rust(
18037        lsp::ServerCapabilities {
18038            completion_provider: Some(lsp::CompletionOptions {
18039                trigger_characters: Some(vec![".".to_string()]),
18040                resolve_provider: Some(true),
18041                ..Default::default()
18042            }),
18043            ..Default::default()
18044        },
18045        cx,
18046    )
18047    .await;
18048
18049    cx.set_state("fn main() { let a = 2ˇ; }");
18050    cx.simulate_keystroke(".");
18051    let completion_item = lsp::CompletionItem {
18052        label: "some".into(),
18053        kind: Some(lsp::CompletionItemKind::SNIPPET),
18054        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18055        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18056            kind: lsp::MarkupKind::Markdown,
18057            value: "```rust\nSome(2)\n```".to_string(),
18058        })),
18059        deprecated: Some(false),
18060        sort_text: Some("fffffff2".to_string()),
18061        filter_text: Some("some".to_string()),
18062        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18063        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18064            range: lsp::Range {
18065                start: lsp::Position {
18066                    line: 0,
18067                    character: 22,
18068                },
18069                end: lsp::Position {
18070                    line: 0,
18071                    character: 22,
18072                },
18073            },
18074            new_text: "Some(2)".to_string(),
18075        })),
18076        additional_text_edits: Some(vec![lsp::TextEdit {
18077            range: lsp::Range {
18078                start: lsp::Position {
18079                    line: 0,
18080                    character: 20,
18081                },
18082                end: lsp::Position {
18083                    line: 0,
18084                    character: 22,
18085                },
18086            },
18087            new_text: "".to_string(),
18088        }]),
18089        ..Default::default()
18090    };
18091
18092    let closure_completion_item = completion_item.clone();
18093    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18094        let task_completion_item = closure_completion_item.clone();
18095        async move {
18096            Ok(Some(lsp::CompletionResponse::Array(vec![
18097                task_completion_item,
18098            ])))
18099        }
18100    });
18101
18102    request.next().await;
18103
18104    cx.condition(|editor, _| editor.context_menu_visible())
18105        .await;
18106    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18107        editor
18108            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18109            .unwrap()
18110    });
18111    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18112
18113    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18114        let task_completion_item = completion_item.clone();
18115        async move { Ok(task_completion_item) }
18116    })
18117    .next()
18118    .await
18119    .unwrap();
18120    apply_additional_edits.await.unwrap();
18121    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18122}
18123
18124#[gpui::test]
18125async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18126    init_test(cx, |_| {});
18127
18128    let mut cx = EditorLspTestContext::new_rust(
18129        lsp::ServerCapabilities {
18130            completion_provider: Some(lsp::CompletionOptions {
18131                trigger_characters: Some(vec![".".to_string()]),
18132                resolve_provider: Some(true),
18133                ..Default::default()
18134            }),
18135            ..Default::default()
18136        },
18137        cx,
18138    )
18139    .await;
18140
18141    cx.set_state("fn main() { let a = 2ˇ; }");
18142    cx.simulate_keystroke(".");
18143
18144    let item1 = lsp::CompletionItem {
18145        label: "method id()".to_string(),
18146        filter_text: Some("id".to_string()),
18147        detail: None,
18148        documentation: None,
18149        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18150            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18151            new_text: ".id".to_string(),
18152        })),
18153        ..lsp::CompletionItem::default()
18154    };
18155
18156    let item2 = lsp::CompletionItem {
18157        label: "other".to_string(),
18158        filter_text: Some("other".to_string()),
18159        detail: None,
18160        documentation: None,
18161        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18162            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18163            new_text: ".other".to_string(),
18164        })),
18165        ..lsp::CompletionItem::default()
18166    };
18167
18168    let item1 = item1.clone();
18169    cx.set_request_handler::<lsp::request::Completion, _, _>({
18170        let item1 = item1.clone();
18171        move |_, _, _| {
18172            let item1 = item1.clone();
18173            let item2 = item2.clone();
18174            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18175        }
18176    })
18177    .next()
18178    .await;
18179
18180    cx.condition(|editor, _| editor.context_menu_visible())
18181        .await;
18182    cx.update_editor(|editor, _, _| {
18183        let context_menu = editor.context_menu.borrow_mut();
18184        let context_menu = context_menu
18185            .as_ref()
18186            .expect("Should have the context menu deployed");
18187        match context_menu {
18188            CodeContextMenu::Completions(completions_menu) => {
18189                let completions = completions_menu.completions.borrow_mut();
18190                assert_eq!(
18191                    completions
18192                        .iter()
18193                        .map(|completion| &completion.label.text)
18194                        .collect::<Vec<_>>(),
18195                    vec!["method id()", "other"]
18196                )
18197            }
18198            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18199        }
18200    });
18201
18202    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18203        let item1 = item1.clone();
18204        move |_, item_to_resolve, _| {
18205            let item1 = item1.clone();
18206            async move {
18207                if item1 == item_to_resolve {
18208                    Ok(lsp::CompletionItem {
18209                        label: "method id()".to_string(),
18210                        filter_text: Some("id".to_string()),
18211                        detail: Some("Now resolved!".to_string()),
18212                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18213                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18214                            range: lsp::Range::new(
18215                                lsp::Position::new(0, 22),
18216                                lsp::Position::new(0, 22),
18217                            ),
18218                            new_text: ".id".to_string(),
18219                        })),
18220                        ..lsp::CompletionItem::default()
18221                    })
18222                } else {
18223                    Ok(item_to_resolve)
18224                }
18225            }
18226        }
18227    })
18228    .next()
18229    .await
18230    .unwrap();
18231    cx.run_until_parked();
18232
18233    cx.update_editor(|editor, window, cx| {
18234        editor.context_menu_next(&Default::default(), window, cx);
18235    });
18236
18237    cx.update_editor(|editor, _, _| {
18238        let context_menu = editor.context_menu.borrow_mut();
18239        let context_menu = context_menu
18240            .as_ref()
18241            .expect("Should have the context menu deployed");
18242        match context_menu {
18243            CodeContextMenu::Completions(completions_menu) => {
18244                let completions = completions_menu.completions.borrow_mut();
18245                assert_eq!(
18246                    completions
18247                        .iter()
18248                        .map(|completion| &completion.label.text)
18249                        .collect::<Vec<_>>(),
18250                    vec!["method id() Now resolved!", "other"],
18251                    "Should update first completion label, but not second as the filter text did not match."
18252                );
18253            }
18254            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18255        }
18256    });
18257}
18258
18259#[gpui::test]
18260async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18261    init_test(cx, |_| {});
18262    let mut cx = EditorLspTestContext::new_rust(
18263        lsp::ServerCapabilities {
18264            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18265            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18266            completion_provider: Some(lsp::CompletionOptions {
18267                resolve_provider: Some(true),
18268                ..Default::default()
18269            }),
18270            ..Default::default()
18271        },
18272        cx,
18273    )
18274    .await;
18275    cx.set_state(indoc! {"
18276        struct TestStruct {
18277            field: i32
18278        }
18279
18280        fn mainˇ() {
18281            let unused_var = 42;
18282            let test_struct = TestStruct { field: 42 };
18283        }
18284    "});
18285    let symbol_range = cx.lsp_range(indoc! {"
18286        struct TestStruct {
18287            field: i32
18288        }
18289
18290        «fn main»() {
18291            let unused_var = 42;
18292            let test_struct = TestStruct { field: 42 };
18293        }
18294    "});
18295    let mut hover_requests =
18296        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18297            Ok(Some(lsp::Hover {
18298                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18299                    kind: lsp::MarkupKind::Markdown,
18300                    value: "Function documentation".to_string(),
18301                }),
18302                range: Some(symbol_range),
18303            }))
18304        });
18305
18306    // Case 1: Test that code action menu hide hover popover
18307    cx.dispatch_action(Hover);
18308    hover_requests.next().await;
18309    cx.condition(|editor, _| editor.hover_state.visible()).await;
18310    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18311        move |_, _, _| async move {
18312            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18313                lsp::CodeAction {
18314                    title: "Remove unused variable".to_string(),
18315                    kind: Some(CodeActionKind::QUICKFIX),
18316                    edit: Some(lsp::WorkspaceEdit {
18317                        changes: Some(
18318                            [(
18319                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18320                                vec![lsp::TextEdit {
18321                                    range: lsp::Range::new(
18322                                        lsp::Position::new(5, 4),
18323                                        lsp::Position::new(5, 27),
18324                                    ),
18325                                    new_text: "".to_string(),
18326                                }],
18327                            )]
18328                            .into_iter()
18329                            .collect(),
18330                        ),
18331                        ..Default::default()
18332                    }),
18333                    ..Default::default()
18334                },
18335            )]))
18336        },
18337    );
18338    cx.update_editor(|editor, window, cx| {
18339        editor.toggle_code_actions(
18340            &ToggleCodeActions {
18341                deployed_from: None,
18342                quick_launch: false,
18343            },
18344            window,
18345            cx,
18346        );
18347    });
18348    code_action_requests.next().await;
18349    cx.run_until_parked();
18350    cx.condition(|editor, _| editor.context_menu_visible())
18351        .await;
18352    cx.update_editor(|editor, _, _| {
18353        assert!(
18354            !editor.hover_state.visible(),
18355            "Hover popover should be hidden when code action menu is shown"
18356        );
18357        // Hide code actions
18358        editor.context_menu.take();
18359    });
18360
18361    // Case 2: Test that code completions hide hover popover
18362    cx.dispatch_action(Hover);
18363    hover_requests.next().await;
18364    cx.condition(|editor, _| editor.hover_state.visible()).await;
18365    let counter = Arc::new(AtomicUsize::new(0));
18366    let mut completion_requests =
18367        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18368            let counter = counter.clone();
18369            async move {
18370                counter.fetch_add(1, atomic::Ordering::Release);
18371                Ok(Some(lsp::CompletionResponse::Array(vec![
18372                    lsp::CompletionItem {
18373                        label: "main".into(),
18374                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18375                        detail: Some("() -> ()".to_string()),
18376                        ..Default::default()
18377                    },
18378                    lsp::CompletionItem {
18379                        label: "TestStruct".into(),
18380                        kind: Some(lsp::CompletionItemKind::STRUCT),
18381                        detail: Some("struct TestStruct".to_string()),
18382                        ..Default::default()
18383                    },
18384                ])))
18385            }
18386        });
18387    cx.update_editor(|editor, window, cx| {
18388        editor.show_completions(&ShowCompletions, window, cx);
18389    });
18390    completion_requests.next().await;
18391    cx.condition(|editor, _| editor.context_menu_visible())
18392        .await;
18393    cx.update_editor(|editor, _, _| {
18394        assert!(
18395            !editor.hover_state.visible(),
18396            "Hover popover should be hidden when completion menu is shown"
18397        );
18398    });
18399}
18400
18401#[gpui::test]
18402async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18403    init_test(cx, |_| {});
18404
18405    let mut cx = EditorLspTestContext::new_rust(
18406        lsp::ServerCapabilities {
18407            completion_provider: Some(lsp::CompletionOptions {
18408                trigger_characters: Some(vec![".".to_string()]),
18409                resolve_provider: Some(true),
18410                ..Default::default()
18411            }),
18412            ..Default::default()
18413        },
18414        cx,
18415    )
18416    .await;
18417
18418    cx.set_state("fn main() { let a = 2ˇ; }");
18419    cx.simulate_keystroke(".");
18420
18421    let unresolved_item_1 = lsp::CompletionItem {
18422        label: "id".to_string(),
18423        filter_text: Some("id".to_string()),
18424        detail: None,
18425        documentation: None,
18426        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18427            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18428            new_text: ".id".to_string(),
18429        })),
18430        ..lsp::CompletionItem::default()
18431    };
18432    let resolved_item_1 = lsp::CompletionItem {
18433        additional_text_edits: Some(vec![lsp::TextEdit {
18434            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18435            new_text: "!!".to_string(),
18436        }]),
18437        ..unresolved_item_1.clone()
18438    };
18439    let unresolved_item_2 = lsp::CompletionItem {
18440        label: "other".to_string(),
18441        filter_text: Some("other".to_string()),
18442        detail: None,
18443        documentation: None,
18444        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18445            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18446            new_text: ".other".to_string(),
18447        })),
18448        ..lsp::CompletionItem::default()
18449    };
18450    let resolved_item_2 = lsp::CompletionItem {
18451        additional_text_edits: Some(vec![lsp::TextEdit {
18452            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18453            new_text: "??".to_string(),
18454        }]),
18455        ..unresolved_item_2.clone()
18456    };
18457
18458    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18459    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18460    cx.lsp
18461        .server
18462        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18463            let unresolved_item_1 = unresolved_item_1.clone();
18464            let resolved_item_1 = resolved_item_1.clone();
18465            let unresolved_item_2 = unresolved_item_2.clone();
18466            let resolved_item_2 = resolved_item_2.clone();
18467            let resolve_requests_1 = resolve_requests_1.clone();
18468            let resolve_requests_2 = resolve_requests_2.clone();
18469            move |unresolved_request, _| {
18470                let unresolved_item_1 = unresolved_item_1.clone();
18471                let resolved_item_1 = resolved_item_1.clone();
18472                let unresolved_item_2 = unresolved_item_2.clone();
18473                let resolved_item_2 = resolved_item_2.clone();
18474                let resolve_requests_1 = resolve_requests_1.clone();
18475                let resolve_requests_2 = resolve_requests_2.clone();
18476                async move {
18477                    if unresolved_request == unresolved_item_1 {
18478                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18479                        Ok(resolved_item_1.clone())
18480                    } else if unresolved_request == unresolved_item_2 {
18481                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18482                        Ok(resolved_item_2.clone())
18483                    } else {
18484                        panic!("Unexpected completion item {unresolved_request:?}")
18485                    }
18486                }
18487            }
18488        })
18489        .detach();
18490
18491    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18492        let unresolved_item_1 = unresolved_item_1.clone();
18493        let unresolved_item_2 = unresolved_item_2.clone();
18494        async move {
18495            Ok(Some(lsp::CompletionResponse::Array(vec![
18496                unresolved_item_1,
18497                unresolved_item_2,
18498            ])))
18499        }
18500    })
18501    .next()
18502    .await;
18503
18504    cx.condition(|editor, _| editor.context_menu_visible())
18505        .await;
18506    cx.update_editor(|editor, _, _| {
18507        let context_menu = editor.context_menu.borrow_mut();
18508        let context_menu = context_menu
18509            .as_ref()
18510            .expect("Should have the context menu deployed");
18511        match context_menu {
18512            CodeContextMenu::Completions(completions_menu) => {
18513                let completions = completions_menu.completions.borrow_mut();
18514                assert_eq!(
18515                    completions
18516                        .iter()
18517                        .map(|completion| &completion.label.text)
18518                        .collect::<Vec<_>>(),
18519                    vec!["id", "other"]
18520                )
18521            }
18522            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18523        }
18524    });
18525    cx.run_until_parked();
18526
18527    cx.update_editor(|editor, window, cx| {
18528        editor.context_menu_next(&ContextMenuNext, window, cx);
18529    });
18530    cx.run_until_parked();
18531    cx.update_editor(|editor, window, cx| {
18532        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18533    });
18534    cx.run_until_parked();
18535    cx.update_editor(|editor, window, cx| {
18536        editor.context_menu_next(&ContextMenuNext, window, cx);
18537    });
18538    cx.run_until_parked();
18539    cx.update_editor(|editor, window, cx| {
18540        editor
18541            .compose_completion(&ComposeCompletion::default(), window, cx)
18542            .expect("No task returned")
18543    })
18544    .await
18545    .expect("Completion failed");
18546    cx.run_until_parked();
18547
18548    cx.update_editor(|editor, _, cx| {
18549        assert_eq!(
18550            resolve_requests_1.load(atomic::Ordering::Acquire),
18551            1,
18552            "Should always resolve once despite multiple selections"
18553        );
18554        assert_eq!(
18555            resolve_requests_2.load(atomic::Ordering::Acquire),
18556            1,
18557            "Should always resolve once after multiple selections and applying the completion"
18558        );
18559        assert_eq!(
18560            editor.text(cx),
18561            "fn main() { let a = ??.other; }",
18562            "Should use resolved data when applying the completion"
18563        );
18564    });
18565}
18566
18567#[gpui::test]
18568async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18569    init_test(cx, |_| {});
18570
18571    let item_0 = lsp::CompletionItem {
18572        label: "abs".into(),
18573        insert_text: Some("abs".into()),
18574        data: Some(json!({ "very": "special"})),
18575        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18576        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18577            lsp::InsertReplaceEdit {
18578                new_text: "abs".to_string(),
18579                insert: lsp::Range::default(),
18580                replace: lsp::Range::default(),
18581            },
18582        )),
18583        ..lsp::CompletionItem::default()
18584    };
18585    let items = iter::once(item_0.clone())
18586        .chain((11..51).map(|i| lsp::CompletionItem {
18587            label: format!("item_{}", i),
18588            insert_text: Some(format!("item_{}", i)),
18589            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18590            ..lsp::CompletionItem::default()
18591        }))
18592        .collect::<Vec<_>>();
18593
18594    let default_commit_characters = vec!["?".to_string()];
18595    let default_data = json!({ "default": "data"});
18596    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18597    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18598    let default_edit_range = lsp::Range {
18599        start: lsp::Position {
18600            line: 0,
18601            character: 5,
18602        },
18603        end: lsp::Position {
18604            line: 0,
18605            character: 5,
18606        },
18607    };
18608
18609    let mut cx = EditorLspTestContext::new_rust(
18610        lsp::ServerCapabilities {
18611            completion_provider: Some(lsp::CompletionOptions {
18612                trigger_characters: Some(vec![".".to_string()]),
18613                resolve_provider: Some(true),
18614                ..Default::default()
18615            }),
18616            ..Default::default()
18617        },
18618        cx,
18619    )
18620    .await;
18621
18622    cx.set_state("fn main() { let a = 2ˇ; }");
18623    cx.simulate_keystroke(".");
18624
18625    let completion_data = default_data.clone();
18626    let completion_characters = default_commit_characters.clone();
18627    let completion_items = items.clone();
18628    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18629        let default_data = completion_data.clone();
18630        let default_commit_characters = completion_characters.clone();
18631        let items = completion_items.clone();
18632        async move {
18633            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18634                items,
18635                item_defaults: Some(lsp::CompletionListItemDefaults {
18636                    data: Some(default_data.clone()),
18637                    commit_characters: Some(default_commit_characters.clone()),
18638                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18639                        default_edit_range,
18640                    )),
18641                    insert_text_format: Some(default_insert_text_format),
18642                    insert_text_mode: Some(default_insert_text_mode),
18643                }),
18644                ..lsp::CompletionList::default()
18645            })))
18646        }
18647    })
18648    .next()
18649    .await;
18650
18651    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18652    cx.lsp
18653        .server
18654        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18655            let closure_resolved_items = resolved_items.clone();
18656            move |item_to_resolve, _| {
18657                let closure_resolved_items = closure_resolved_items.clone();
18658                async move {
18659                    closure_resolved_items.lock().push(item_to_resolve.clone());
18660                    Ok(item_to_resolve)
18661                }
18662            }
18663        })
18664        .detach();
18665
18666    cx.condition(|editor, _| editor.context_menu_visible())
18667        .await;
18668    cx.run_until_parked();
18669    cx.update_editor(|editor, _, _| {
18670        let menu = editor.context_menu.borrow_mut();
18671        match menu.as_ref().expect("should have the completions menu") {
18672            CodeContextMenu::Completions(completions_menu) => {
18673                assert_eq!(
18674                    completions_menu
18675                        .entries
18676                        .borrow()
18677                        .iter()
18678                        .map(|mat| mat.string.clone())
18679                        .collect::<Vec<String>>(),
18680                    items
18681                        .iter()
18682                        .map(|completion| completion.label.clone())
18683                        .collect::<Vec<String>>()
18684                );
18685            }
18686            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18687        }
18688    });
18689    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18690    // with 4 from the end.
18691    assert_eq!(
18692        *resolved_items.lock(),
18693        [&items[0..16], &items[items.len() - 4..items.len()]]
18694            .concat()
18695            .iter()
18696            .cloned()
18697            .map(|mut item| {
18698                if item.data.is_none() {
18699                    item.data = Some(default_data.clone());
18700                }
18701                item
18702            })
18703            .collect::<Vec<lsp::CompletionItem>>(),
18704        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18705    );
18706    resolved_items.lock().clear();
18707
18708    cx.update_editor(|editor, window, cx| {
18709        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18710    });
18711    cx.run_until_parked();
18712    // Completions that have already been resolved are skipped.
18713    assert_eq!(
18714        *resolved_items.lock(),
18715        items[items.len() - 17..items.len() - 4]
18716            .iter()
18717            .cloned()
18718            .map(|mut item| {
18719                if item.data.is_none() {
18720                    item.data = Some(default_data.clone());
18721                }
18722                item
18723            })
18724            .collect::<Vec<lsp::CompletionItem>>()
18725    );
18726    resolved_items.lock().clear();
18727}
18728
18729#[gpui::test]
18730async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18731    init_test(cx, |_| {});
18732
18733    let mut cx = EditorLspTestContext::new(
18734        Language::new(
18735            LanguageConfig {
18736                matcher: LanguageMatcher {
18737                    path_suffixes: vec!["jsx".into()],
18738                    ..Default::default()
18739                },
18740                overrides: [(
18741                    "element".into(),
18742                    LanguageConfigOverride {
18743                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18744                        ..Default::default()
18745                    },
18746                )]
18747                .into_iter()
18748                .collect(),
18749                ..Default::default()
18750            },
18751            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18752        )
18753        .with_override_query("(jsx_self_closing_element) @element")
18754        .unwrap(),
18755        lsp::ServerCapabilities {
18756            completion_provider: Some(lsp::CompletionOptions {
18757                trigger_characters: Some(vec![":".to_string()]),
18758                ..Default::default()
18759            }),
18760            ..Default::default()
18761        },
18762        cx,
18763    )
18764    .await;
18765
18766    cx.lsp
18767        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18768            Ok(Some(lsp::CompletionResponse::Array(vec![
18769                lsp::CompletionItem {
18770                    label: "bg-blue".into(),
18771                    ..Default::default()
18772                },
18773                lsp::CompletionItem {
18774                    label: "bg-red".into(),
18775                    ..Default::default()
18776                },
18777                lsp::CompletionItem {
18778                    label: "bg-yellow".into(),
18779                    ..Default::default()
18780                },
18781            ])))
18782        });
18783
18784    cx.set_state(r#"<p class="bgˇ" />"#);
18785
18786    // Trigger completion when typing a dash, because the dash is an extra
18787    // word character in the 'element' scope, which contains the cursor.
18788    cx.simulate_keystroke("-");
18789    cx.executor().run_until_parked();
18790    cx.update_editor(|editor, _, _| {
18791        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18792        {
18793            assert_eq!(
18794                completion_menu_entries(menu),
18795                &["bg-blue", "bg-red", "bg-yellow"]
18796            );
18797        } else {
18798            panic!("expected completion menu to be open");
18799        }
18800    });
18801
18802    cx.simulate_keystroke("l");
18803    cx.executor().run_until_parked();
18804    cx.update_editor(|editor, _, _| {
18805        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18806        {
18807            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18808        } else {
18809            panic!("expected completion menu to be open");
18810        }
18811    });
18812
18813    // When filtering completions, consider the character after the '-' to
18814    // be the start of a subword.
18815    cx.set_state(r#"<p class="yelˇ" />"#);
18816    cx.simulate_keystroke("l");
18817    cx.executor().run_until_parked();
18818    cx.update_editor(|editor, _, _| {
18819        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18820        {
18821            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18822        } else {
18823            panic!("expected completion menu to be open");
18824        }
18825    });
18826}
18827
18828fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18829    let entries = menu.entries.borrow();
18830    entries.iter().map(|mat| mat.string.clone()).collect()
18831}
18832
18833#[gpui::test]
18834async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18835    init_test(cx, |settings| {
18836        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18837    });
18838
18839    let fs = FakeFs::new(cx.executor());
18840    fs.insert_file(path!("/file.ts"), Default::default()).await;
18841
18842    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18843    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18844
18845    language_registry.add(Arc::new(Language::new(
18846        LanguageConfig {
18847            name: "TypeScript".into(),
18848            matcher: LanguageMatcher {
18849                path_suffixes: vec!["ts".to_string()],
18850                ..Default::default()
18851            },
18852            ..Default::default()
18853        },
18854        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18855    )));
18856    update_test_language_settings(cx, |settings| {
18857        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18858    });
18859
18860    let test_plugin = "test_plugin";
18861    let _ = language_registry.register_fake_lsp(
18862        "TypeScript",
18863        FakeLspAdapter {
18864            prettier_plugins: vec![test_plugin],
18865            ..Default::default()
18866        },
18867    );
18868
18869    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18870    let buffer = project
18871        .update(cx, |project, cx| {
18872            project.open_local_buffer(path!("/file.ts"), cx)
18873        })
18874        .await
18875        .unwrap();
18876
18877    let buffer_text = "one\ntwo\nthree\n";
18878    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18879    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18880    editor.update_in(cx, |editor, window, cx| {
18881        editor.set_text(buffer_text, window, cx)
18882    });
18883
18884    editor
18885        .update_in(cx, |editor, window, cx| {
18886            editor.perform_format(
18887                project.clone(),
18888                FormatTrigger::Manual,
18889                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18890                window,
18891                cx,
18892            )
18893        })
18894        .unwrap()
18895        .await;
18896    assert_eq!(
18897        editor.update(cx, |editor, cx| editor.text(cx)),
18898        buffer_text.to_string() + prettier_format_suffix,
18899        "Test prettier formatting was not applied to the original buffer text",
18900    );
18901
18902    update_test_language_settings(cx, |settings| {
18903        settings.defaults.formatter = Some(FormatterList::default())
18904    });
18905    let format = editor.update_in(cx, |editor, window, cx| {
18906        editor.perform_format(
18907            project.clone(),
18908            FormatTrigger::Manual,
18909            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18910            window,
18911            cx,
18912        )
18913    });
18914    format.await.unwrap();
18915    assert_eq!(
18916        editor.update(cx, |editor, cx| editor.text(cx)),
18917        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18918        "Autoformatting (via test prettier) was not applied to the original buffer text",
18919    );
18920}
18921
18922#[gpui::test]
18923async fn test_addition_reverts(cx: &mut TestAppContext) {
18924    init_test(cx, |_| {});
18925    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18926    let base_text = indoc! {r#"
18927        struct Row;
18928        struct Row1;
18929        struct Row2;
18930
18931        struct Row4;
18932        struct Row5;
18933        struct Row6;
18934
18935        struct Row8;
18936        struct Row9;
18937        struct Row10;"#};
18938
18939    // When addition hunks are not adjacent to carets, no hunk revert is performed
18940    assert_hunk_revert(
18941        indoc! {r#"struct Row;
18942                   struct Row1;
18943                   struct Row1.1;
18944                   struct Row1.2;
18945                   struct Row2;ˇ
18946
18947                   struct Row4;
18948                   struct Row5;
18949                   struct Row6;
18950
18951                   struct Row8;
18952                   ˇstruct Row9;
18953                   struct Row9.1;
18954                   struct Row9.2;
18955                   struct Row9.3;
18956                   struct Row10;"#},
18957        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18958        indoc! {r#"struct Row;
18959                   struct Row1;
18960                   struct Row1.1;
18961                   struct Row1.2;
18962                   struct Row2;ˇ
18963
18964                   struct Row4;
18965                   struct Row5;
18966                   struct Row6;
18967
18968                   struct Row8;
18969                   ˇstruct Row9;
18970                   struct Row9.1;
18971                   struct Row9.2;
18972                   struct Row9.3;
18973                   struct Row10;"#},
18974        base_text,
18975        &mut cx,
18976    );
18977    // Same for selections
18978    assert_hunk_revert(
18979        indoc! {r#"struct Row;
18980                   struct Row1;
18981                   struct Row2;
18982                   struct Row2.1;
18983                   struct Row2.2;
18984                   «ˇ
18985                   struct Row4;
18986                   struct» Row5;
18987                   «struct Row6;
18988                   ˇ»
18989                   struct Row9.1;
18990                   struct Row9.2;
18991                   struct Row9.3;
18992                   struct Row8;
18993                   struct Row9;
18994                   struct Row10;"#},
18995        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18996        indoc! {r#"struct Row;
18997                   struct Row1;
18998                   struct Row2;
18999                   struct Row2.1;
19000                   struct Row2.2;
19001                   «ˇ
19002                   struct Row4;
19003                   struct» Row5;
19004                   «struct Row6;
19005                   ˇ»
19006                   struct Row9.1;
19007                   struct Row9.2;
19008                   struct Row9.3;
19009                   struct Row8;
19010                   struct Row9;
19011                   struct Row10;"#},
19012        base_text,
19013        &mut cx,
19014    );
19015
19016    // When carets and selections intersect the addition hunks, those are reverted.
19017    // Adjacent carets got merged.
19018    assert_hunk_revert(
19019        indoc! {r#"struct Row;
19020                   ˇ// something on the top
19021                   struct Row1;
19022                   struct Row2;
19023                   struct Roˇw3.1;
19024                   struct Row2.2;
19025                   struct Row2.3;ˇ
19026
19027                   struct Row4;
19028                   struct ˇRow5.1;
19029                   struct Row5.2;
19030                   struct «Rowˇ»5.3;
19031                   struct Row5;
19032                   struct Row6;
19033                   ˇ
19034                   struct Row9.1;
19035                   struct «Rowˇ»9.2;
19036                   struct «ˇRow»9.3;
19037                   struct Row8;
19038                   struct Row9;
19039                   «ˇ// something on bottom»
19040                   struct Row10;"#},
19041        vec![
19042            DiffHunkStatusKind::Added,
19043            DiffHunkStatusKind::Added,
19044            DiffHunkStatusKind::Added,
19045            DiffHunkStatusKind::Added,
19046            DiffHunkStatusKind::Added,
19047        ],
19048        indoc! {r#"struct Row;
19049                   ˇstruct Row1;
19050                   struct Row2;
19051                   ˇ
19052                   struct Row4;
19053                   ˇstruct Row5;
19054                   struct Row6;
19055                   ˇ
19056                   ˇstruct Row8;
19057                   struct Row9;
19058                   ˇstruct Row10;"#},
19059        base_text,
19060        &mut cx,
19061    );
19062}
19063
19064#[gpui::test]
19065async fn test_modification_reverts(cx: &mut TestAppContext) {
19066    init_test(cx, |_| {});
19067    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19068    let base_text = indoc! {r#"
19069        struct Row;
19070        struct Row1;
19071        struct Row2;
19072
19073        struct Row4;
19074        struct Row5;
19075        struct Row6;
19076
19077        struct Row8;
19078        struct Row9;
19079        struct Row10;"#};
19080
19081    // Modification hunks behave the same as the addition ones.
19082    assert_hunk_revert(
19083        indoc! {r#"struct Row;
19084                   struct Row1;
19085                   struct Row33;
19086                   ˇ
19087                   struct Row4;
19088                   struct Row5;
19089                   struct Row6;
19090                   ˇ
19091                   struct Row99;
19092                   struct Row9;
19093                   struct Row10;"#},
19094        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19095        indoc! {r#"struct Row;
19096                   struct Row1;
19097                   struct Row33;
19098                   ˇ
19099                   struct Row4;
19100                   struct Row5;
19101                   struct Row6;
19102                   ˇ
19103                   struct Row99;
19104                   struct Row9;
19105                   struct Row10;"#},
19106        base_text,
19107        &mut cx,
19108    );
19109    assert_hunk_revert(
19110        indoc! {r#"struct Row;
19111                   struct Row1;
19112                   struct Row33;
19113                   «ˇ
19114                   struct Row4;
19115                   struct» Row5;
19116                   «struct Row6;
19117                   ˇ»
19118                   struct Row99;
19119                   struct Row9;
19120                   struct Row10;"#},
19121        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19122        indoc! {r#"struct Row;
19123                   struct Row1;
19124                   struct Row33;
19125                   «ˇ
19126                   struct Row4;
19127                   struct» Row5;
19128                   «struct Row6;
19129                   ˇ»
19130                   struct Row99;
19131                   struct Row9;
19132                   struct Row10;"#},
19133        base_text,
19134        &mut cx,
19135    );
19136
19137    assert_hunk_revert(
19138        indoc! {r#"ˇstruct Row1.1;
19139                   struct Row1;
19140                   «ˇstr»uct Row22;
19141
19142                   struct ˇRow44;
19143                   struct Row5;
19144                   struct «Rˇ»ow66;ˇ
19145
19146                   «struˇ»ct Row88;
19147                   struct Row9;
19148                   struct Row1011;ˇ"#},
19149        vec![
19150            DiffHunkStatusKind::Modified,
19151            DiffHunkStatusKind::Modified,
19152            DiffHunkStatusKind::Modified,
19153            DiffHunkStatusKind::Modified,
19154            DiffHunkStatusKind::Modified,
19155            DiffHunkStatusKind::Modified,
19156        ],
19157        indoc! {r#"struct Row;
19158                   ˇstruct Row1;
19159                   struct Row2;
19160                   ˇ
19161                   struct Row4;
19162                   ˇstruct Row5;
19163                   struct Row6;
19164                   ˇ
19165                   struct Row8;
19166                   ˇstruct Row9;
19167                   struct Row10;ˇ"#},
19168        base_text,
19169        &mut cx,
19170    );
19171}
19172
19173#[gpui::test]
19174async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19175    init_test(cx, |_| {});
19176    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19177    let base_text = indoc! {r#"
19178        one
19179
19180        two
19181        three
19182        "#};
19183
19184    cx.set_head_text(base_text);
19185    cx.set_state("\nˇ\n");
19186    cx.executor().run_until_parked();
19187    cx.update_editor(|editor, _window, cx| {
19188        editor.expand_selected_diff_hunks(cx);
19189    });
19190    cx.executor().run_until_parked();
19191    cx.update_editor(|editor, window, cx| {
19192        editor.backspace(&Default::default(), window, cx);
19193    });
19194    cx.run_until_parked();
19195    cx.assert_state_with_diff(
19196        indoc! {r#"
19197
19198        - two
19199        - threeˇ
19200        +
19201        "#}
19202        .to_string(),
19203    );
19204}
19205
19206#[gpui::test]
19207async fn test_deletion_reverts(cx: &mut TestAppContext) {
19208    init_test(cx, |_| {});
19209    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19210    let base_text = indoc! {r#"struct Row;
19211struct Row1;
19212struct Row2;
19213
19214struct Row4;
19215struct Row5;
19216struct Row6;
19217
19218struct Row8;
19219struct Row9;
19220struct Row10;"#};
19221
19222    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19223    assert_hunk_revert(
19224        indoc! {r#"struct Row;
19225                   struct Row2;
19226
19227                   ˇstruct Row4;
19228                   struct Row5;
19229                   struct Row6;
19230                   ˇ
19231                   struct Row8;
19232                   struct Row10;"#},
19233        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19234        indoc! {r#"struct Row;
19235                   struct Row2;
19236
19237                   ˇstruct Row4;
19238                   struct Row5;
19239                   struct Row6;
19240                   ˇ
19241                   struct Row8;
19242                   struct Row10;"#},
19243        base_text,
19244        &mut cx,
19245    );
19246    assert_hunk_revert(
19247        indoc! {r#"struct Row;
19248                   struct Row2;
19249
19250                   «ˇstruct Row4;
19251                   struct» Row5;
19252                   «struct Row6;
19253                   ˇ»
19254                   struct Row8;
19255                   struct Row10;"#},
19256        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19257        indoc! {r#"struct Row;
19258                   struct Row2;
19259
19260                   «ˇstruct Row4;
19261                   struct» Row5;
19262                   «struct Row6;
19263                   ˇ»
19264                   struct Row8;
19265                   struct Row10;"#},
19266        base_text,
19267        &mut cx,
19268    );
19269
19270    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19271    assert_hunk_revert(
19272        indoc! {r#"struct Row;
19273                   ˇstruct Row2;
19274
19275                   struct Row4;
19276                   struct Row5;
19277                   struct Row6;
19278
19279                   struct Row8;ˇ
19280                   struct Row10;"#},
19281        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19282        indoc! {r#"struct Row;
19283                   struct Row1;
19284                   ˇstruct Row2;
19285
19286                   struct Row4;
19287                   struct Row5;
19288                   struct Row6;
19289
19290                   struct Row8;ˇ
19291                   struct Row9;
19292                   struct Row10;"#},
19293        base_text,
19294        &mut cx,
19295    );
19296    assert_hunk_revert(
19297        indoc! {r#"struct Row;
19298                   struct Row2«ˇ;
19299                   struct Row4;
19300                   struct» Row5;
19301                   «struct Row6;
19302
19303                   struct Row8;ˇ»
19304                   struct Row10;"#},
19305        vec![
19306            DiffHunkStatusKind::Deleted,
19307            DiffHunkStatusKind::Deleted,
19308            DiffHunkStatusKind::Deleted,
19309        ],
19310        indoc! {r#"struct Row;
19311                   struct Row1;
19312                   struct Row2«ˇ;
19313
19314                   struct Row4;
19315                   struct» Row5;
19316                   «struct Row6;
19317
19318                   struct Row8;ˇ»
19319                   struct Row9;
19320                   struct Row10;"#},
19321        base_text,
19322        &mut cx,
19323    );
19324}
19325
19326#[gpui::test]
19327async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19328    init_test(cx, |_| {});
19329
19330    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19331    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19332    let base_text_3 =
19333        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19334
19335    let text_1 = edit_first_char_of_every_line(base_text_1);
19336    let text_2 = edit_first_char_of_every_line(base_text_2);
19337    let text_3 = edit_first_char_of_every_line(base_text_3);
19338
19339    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19340    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19341    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19342
19343    let multibuffer = cx.new(|cx| {
19344        let mut multibuffer = MultiBuffer::new(ReadWrite);
19345        multibuffer.push_excerpts(
19346            buffer_1.clone(),
19347            [
19348                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19349                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19350                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19351            ],
19352            cx,
19353        );
19354        multibuffer.push_excerpts(
19355            buffer_2.clone(),
19356            [
19357                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19358                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19359                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19360            ],
19361            cx,
19362        );
19363        multibuffer.push_excerpts(
19364            buffer_3.clone(),
19365            [
19366                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19367                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19368                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19369            ],
19370            cx,
19371        );
19372        multibuffer
19373    });
19374
19375    let fs = FakeFs::new(cx.executor());
19376    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19377    let (editor, cx) = cx
19378        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19379    editor.update_in(cx, |editor, _window, cx| {
19380        for (buffer, diff_base) in [
19381            (buffer_1.clone(), base_text_1),
19382            (buffer_2.clone(), base_text_2),
19383            (buffer_3.clone(), base_text_3),
19384        ] {
19385            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19386            editor
19387                .buffer
19388                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19389        }
19390    });
19391    cx.executor().run_until_parked();
19392
19393    editor.update_in(cx, |editor, window, cx| {
19394        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}");
19395        editor.select_all(&SelectAll, window, cx);
19396        editor.git_restore(&Default::default(), window, cx);
19397    });
19398    cx.executor().run_until_parked();
19399
19400    // When all ranges are selected, all buffer hunks are reverted.
19401    editor.update(cx, |editor, cx| {
19402        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");
19403    });
19404    buffer_1.update(cx, |buffer, _| {
19405        assert_eq!(buffer.text(), base_text_1);
19406    });
19407    buffer_2.update(cx, |buffer, _| {
19408        assert_eq!(buffer.text(), base_text_2);
19409    });
19410    buffer_3.update(cx, |buffer, _| {
19411        assert_eq!(buffer.text(), base_text_3);
19412    });
19413
19414    editor.update_in(cx, |editor, window, cx| {
19415        editor.undo(&Default::default(), window, cx);
19416    });
19417
19418    editor.update_in(cx, |editor, window, cx| {
19419        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19420            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19421        });
19422        editor.git_restore(&Default::default(), window, cx);
19423    });
19424
19425    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19426    // but not affect buffer_2 and its related excerpts.
19427    editor.update(cx, |editor, cx| {
19428        assert_eq!(
19429            editor.text(cx),
19430            "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}"
19431        );
19432    });
19433    buffer_1.update(cx, |buffer, _| {
19434        assert_eq!(buffer.text(), base_text_1);
19435    });
19436    buffer_2.update(cx, |buffer, _| {
19437        assert_eq!(
19438            buffer.text(),
19439            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19440        );
19441    });
19442    buffer_3.update(cx, |buffer, _| {
19443        assert_eq!(
19444            buffer.text(),
19445            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19446        );
19447    });
19448
19449    fn edit_first_char_of_every_line(text: &str) -> String {
19450        text.split('\n')
19451            .map(|line| format!("X{}", &line[1..]))
19452            .collect::<Vec<_>>()
19453            .join("\n")
19454    }
19455}
19456
19457#[gpui::test]
19458async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19459    init_test(cx, |_| {});
19460
19461    let cols = 4;
19462    let rows = 10;
19463    let sample_text_1 = sample_text(rows, cols, 'a');
19464    assert_eq!(
19465        sample_text_1,
19466        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19467    );
19468    let sample_text_2 = sample_text(rows, cols, 'l');
19469    assert_eq!(
19470        sample_text_2,
19471        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19472    );
19473    let sample_text_3 = sample_text(rows, cols, 'v');
19474    assert_eq!(
19475        sample_text_3,
19476        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19477    );
19478
19479    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19480    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19481    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19482
19483    let multi_buffer = cx.new(|cx| {
19484        let mut multibuffer = MultiBuffer::new(ReadWrite);
19485        multibuffer.push_excerpts(
19486            buffer_1.clone(),
19487            [
19488                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19489                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19490                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19491            ],
19492            cx,
19493        );
19494        multibuffer.push_excerpts(
19495            buffer_2.clone(),
19496            [
19497                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19498                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19499                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19500            ],
19501            cx,
19502        );
19503        multibuffer.push_excerpts(
19504            buffer_3.clone(),
19505            [
19506                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19507                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19508                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19509            ],
19510            cx,
19511        );
19512        multibuffer
19513    });
19514
19515    let fs = FakeFs::new(cx.executor());
19516    fs.insert_tree(
19517        "/a",
19518        json!({
19519            "main.rs": sample_text_1,
19520            "other.rs": sample_text_2,
19521            "lib.rs": sample_text_3,
19522        }),
19523    )
19524    .await;
19525    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19526    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19527    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19528    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19529        Editor::new(
19530            EditorMode::full(),
19531            multi_buffer,
19532            Some(project.clone()),
19533            window,
19534            cx,
19535        )
19536    });
19537    let multibuffer_item_id = workspace
19538        .update(cx, |workspace, window, cx| {
19539            assert!(
19540                workspace.active_item(cx).is_none(),
19541                "active item should be None before the first item is added"
19542            );
19543            workspace.add_item_to_active_pane(
19544                Box::new(multi_buffer_editor.clone()),
19545                None,
19546                true,
19547                window,
19548                cx,
19549            );
19550            let active_item = workspace
19551                .active_item(cx)
19552                .expect("should have an active item after adding the multi buffer");
19553            assert_eq!(
19554                active_item.buffer_kind(cx),
19555                ItemBufferKind::Multibuffer,
19556                "A multi buffer was expected to active after adding"
19557            );
19558            active_item.item_id()
19559        })
19560        .unwrap();
19561    cx.executor().run_until_parked();
19562
19563    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19564        editor.change_selections(
19565            SelectionEffects::scroll(Autoscroll::Next),
19566            window,
19567            cx,
19568            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
19569        );
19570        editor.open_excerpts(&OpenExcerpts, window, cx);
19571    });
19572    cx.executor().run_until_parked();
19573    let first_item_id = workspace
19574        .update(cx, |workspace, window, cx| {
19575            let active_item = workspace
19576                .active_item(cx)
19577                .expect("should have an active item after navigating into the 1st buffer");
19578            let first_item_id = active_item.item_id();
19579            assert_ne!(
19580                first_item_id, multibuffer_item_id,
19581                "Should navigate into the 1st buffer and activate it"
19582            );
19583            assert_eq!(
19584                active_item.buffer_kind(cx),
19585                ItemBufferKind::Singleton,
19586                "New active item should be a singleton buffer"
19587            );
19588            assert_eq!(
19589                active_item
19590                    .act_as::<Editor>(cx)
19591                    .expect("should have navigated into an editor for the 1st buffer")
19592                    .read(cx)
19593                    .text(cx),
19594                sample_text_1
19595            );
19596
19597            workspace
19598                .go_back(workspace.active_pane().downgrade(), window, cx)
19599                .detach_and_log_err(cx);
19600
19601            first_item_id
19602        })
19603        .unwrap();
19604    cx.executor().run_until_parked();
19605    workspace
19606        .update(cx, |workspace, _, cx| {
19607            let active_item = workspace
19608                .active_item(cx)
19609                .expect("should have an active item after navigating back");
19610            assert_eq!(
19611                active_item.item_id(),
19612                multibuffer_item_id,
19613                "Should navigate back to the multi buffer"
19614            );
19615            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19616        })
19617        .unwrap();
19618
19619    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19620        editor.change_selections(
19621            SelectionEffects::scroll(Autoscroll::Next),
19622            window,
19623            cx,
19624            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
19625        );
19626        editor.open_excerpts(&OpenExcerpts, window, cx);
19627    });
19628    cx.executor().run_until_parked();
19629    let second_item_id = workspace
19630        .update(cx, |workspace, window, cx| {
19631            let active_item = workspace
19632                .active_item(cx)
19633                .expect("should have an active item after navigating into the 2nd buffer");
19634            let second_item_id = active_item.item_id();
19635            assert_ne!(
19636                second_item_id, multibuffer_item_id,
19637                "Should navigate away from the multibuffer"
19638            );
19639            assert_ne!(
19640                second_item_id, first_item_id,
19641                "Should navigate into the 2nd buffer and activate it"
19642            );
19643            assert_eq!(
19644                active_item.buffer_kind(cx),
19645                ItemBufferKind::Singleton,
19646                "New active item should be a singleton buffer"
19647            );
19648            assert_eq!(
19649                active_item
19650                    .act_as::<Editor>(cx)
19651                    .expect("should have navigated into an editor")
19652                    .read(cx)
19653                    .text(cx),
19654                sample_text_2
19655            );
19656
19657            workspace
19658                .go_back(workspace.active_pane().downgrade(), window, cx)
19659                .detach_and_log_err(cx);
19660
19661            second_item_id
19662        })
19663        .unwrap();
19664    cx.executor().run_until_parked();
19665    workspace
19666        .update(cx, |workspace, _, cx| {
19667            let active_item = workspace
19668                .active_item(cx)
19669                .expect("should have an active item after navigating back from the 2nd buffer");
19670            assert_eq!(
19671                active_item.item_id(),
19672                multibuffer_item_id,
19673                "Should navigate back from the 2nd buffer to the multi buffer"
19674            );
19675            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19676        })
19677        .unwrap();
19678
19679    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19680        editor.change_selections(
19681            SelectionEffects::scroll(Autoscroll::Next),
19682            window,
19683            cx,
19684            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
19685        );
19686        editor.open_excerpts(&OpenExcerpts, window, cx);
19687    });
19688    cx.executor().run_until_parked();
19689    workspace
19690        .update(cx, |workspace, window, cx| {
19691            let active_item = workspace
19692                .active_item(cx)
19693                .expect("should have an active item after navigating into the 3rd buffer");
19694            let third_item_id = active_item.item_id();
19695            assert_ne!(
19696                third_item_id, multibuffer_item_id,
19697                "Should navigate into the 3rd buffer and activate it"
19698            );
19699            assert_ne!(third_item_id, first_item_id);
19700            assert_ne!(third_item_id, second_item_id);
19701            assert_eq!(
19702                active_item.buffer_kind(cx),
19703                ItemBufferKind::Singleton,
19704                "New active item should be a singleton buffer"
19705            );
19706            assert_eq!(
19707                active_item
19708                    .act_as::<Editor>(cx)
19709                    .expect("should have navigated into an editor")
19710                    .read(cx)
19711                    .text(cx),
19712                sample_text_3
19713            );
19714
19715            workspace
19716                .go_back(workspace.active_pane().downgrade(), window, cx)
19717                .detach_and_log_err(cx);
19718        })
19719        .unwrap();
19720    cx.executor().run_until_parked();
19721    workspace
19722        .update(cx, |workspace, _, cx| {
19723            let active_item = workspace
19724                .active_item(cx)
19725                .expect("should have an active item after navigating back from the 3rd buffer");
19726            assert_eq!(
19727                active_item.item_id(),
19728                multibuffer_item_id,
19729                "Should navigate back from the 3rd buffer to the multi buffer"
19730            );
19731            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19732        })
19733        .unwrap();
19734}
19735
19736#[gpui::test]
19737async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19738    init_test(cx, |_| {});
19739
19740    let mut cx = EditorTestContext::new(cx).await;
19741
19742    let diff_base = r#"
19743        use some::mod;
19744
19745        const A: u32 = 42;
19746
19747        fn main() {
19748            println!("hello");
19749
19750            println!("world");
19751        }
19752        "#
19753    .unindent();
19754
19755    cx.set_state(
19756        &r#"
19757        use some::modified;
19758
19759        ˇ
19760        fn main() {
19761            println!("hello there");
19762
19763            println!("around the");
19764            println!("world");
19765        }
19766        "#
19767        .unindent(),
19768    );
19769
19770    cx.set_head_text(&diff_base);
19771    executor.run_until_parked();
19772
19773    cx.update_editor(|editor, window, cx| {
19774        editor.go_to_next_hunk(&GoToHunk, window, cx);
19775        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19776    });
19777    executor.run_until_parked();
19778    cx.assert_state_with_diff(
19779        r#"
19780          use some::modified;
19781
19782
19783          fn main() {
19784        -     println!("hello");
19785        + ˇ    println!("hello there");
19786
19787              println!("around the");
19788              println!("world");
19789          }
19790        "#
19791        .unindent(),
19792    );
19793
19794    cx.update_editor(|editor, window, cx| {
19795        for _ in 0..2 {
19796            editor.go_to_next_hunk(&GoToHunk, window, cx);
19797            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19798        }
19799    });
19800    executor.run_until_parked();
19801    cx.assert_state_with_diff(
19802        r#"
19803        - use some::mod;
19804        + ˇuse some::modified;
19805
19806
19807          fn main() {
19808        -     println!("hello");
19809        +     println!("hello there");
19810
19811        +     println!("around the");
19812              println!("world");
19813          }
19814        "#
19815        .unindent(),
19816    );
19817
19818    cx.update_editor(|editor, window, cx| {
19819        editor.go_to_next_hunk(&GoToHunk, window, cx);
19820        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19821    });
19822    executor.run_until_parked();
19823    cx.assert_state_with_diff(
19824        r#"
19825        - use some::mod;
19826        + use some::modified;
19827
19828        - const A: u32 = 42;
19829          ˇ
19830          fn main() {
19831        -     println!("hello");
19832        +     println!("hello there");
19833
19834        +     println!("around the");
19835              println!("world");
19836          }
19837        "#
19838        .unindent(),
19839    );
19840
19841    cx.update_editor(|editor, window, cx| {
19842        editor.cancel(&Cancel, window, cx);
19843    });
19844
19845    cx.assert_state_with_diff(
19846        r#"
19847          use some::modified;
19848
19849          ˇ
19850          fn main() {
19851              println!("hello there");
19852
19853              println!("around the");
19854              println!("world");
19855          }
19856        "#
19857        .unindent(),
19858    );
19859}
19860
19861#[gpui::test]
19862async fn test_diff_base_change_with_expanded_diff_hunks(
19863    executor: BackgroundExecutor,
19864    cx: &mut TestAppContext,
19865) {
19866    init_test(cx, |_| {});
19867
19868    let mut cx = EditorTestContext::new(cx).await;
19869
19870    let diff_base = r#"
19871        use some::mod1;
19872        use some::mod2;
19873
19874        const A: u32 = 42;
19875        const B: u32 = 42;
19876        const C: u32 = 42;
19877
19878        fn main() {
19879            println!("hello");
19880
19881            println!("world");
19882        }
19883        "#
19884    .unindent();
19885
19886    cx.set_state(
19887        &r#"
19888        use some::mod2;
19889
19890        const A: u32 = 42;
19891        const C: u32 = 42;
19892
19893        fn main(ˇ) {
19894            //println!("hello");
19895
19896            println!("world");
19897            //
19898            //
19899        }
19900        "#
19901        .unindent(),
19902    );
19903
19904    cx.set_head_text(&diff_base);
19905    executor.run_until_parked();
19906
19907    cx.update_editor(|editor, window, cx| {
19908        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, 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          const C: u32 = 42;
19919
19920          fn main(ˇ) {
19921        -     println!("hello");
19922        +     //println!("hello");
19923
19924              println!("world");
19925        +     //
19926        +     //
19927          }
19928        "#
19929        .unindent(),
19930    );
19931
19932    cx.set_head_text("new diff base!");
19933    executor.run_until_parked();
19934    cx.assert_state_with_diff(
19935        r#"
19936        - new diff base!
19937        + use some::mod2;
19938        +
19939        + const A: u32 = 42;
19940        + const C: u32 = 42;
19941        +
19942        + fn main(ˇ) {
19943        +     //println!("hello");
19944        +
19945        +     println!("world");
19946        +     //
19947        +     //
19948        + }
19949        "#
19950        .unindent(),
19951    );
19952}
19953
19954#[gpui::test]
19955async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19956    init_test(cx, |_| {});
19957
19958    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19959    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19960    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19961    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19962    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19963    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19964
19965    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19966    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19967    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19968
19969    let multi_buffer = cx.new(|cx| {
19970        let mut multibuffer = MultiBuffer::new(ReadWrite);
19971        multibuffer.push_excerpts(
19972            buffer_1.clone(),
19973            [
19974                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19975                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19976                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19977            ],
19978            cx,
19979        );
19980        multibuffer.push_excerpts(
19981            buffer_2.clone(),
19982            [
19983                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19984                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19985                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19986            ],
19987            cx,
19988        );
19989        multibuffer.push_excerpts(
19990            buffer_3.clone(),
19991            [
19992                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19993                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19994                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19995            ],
19996            cx,
19997        );
19998        multibuffer
19999    });
20000
20001    let editor =
20002        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20003    editor
20004        .update(cx, |editor, _window, cx| {
20005            for (buffer, diff_base) in [
20006                (buffer_1.clone(), file_1_old),
20007                (buffer_2.clone(), file_2_old),
20008                (buffer_3.clone(), file_3_old),
20009            ] {
20010                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
20011                editor
20012                    .buffer
20013                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20014            }
20015        })
20016        .unwrap();
20017
20018    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20019    cx.run_until_parked();
20020
20021    cx.assert_editor_state(
20022        &"
20023            ˇaaa
20024            ccc
20025            ddd
20026
20027            ggg
20028            hhh
20029
20030
20031            lll
20032            mmm
20033            NNN
20034
20035            qqq
20036            rrr
20037
20038            uuu
20039            111
20040            222
20041            333
20042
20043            666
20044            777
20045
20046            000
20047            !!!"
20048        .unindent(),
20049    );
20050
20051    cx.update_editor(|editor, window, cx| {
20052        editor.select_all(&SelectAll, window, cx);
20053        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20054    });
20055    cx.executor().run_until_parked();
20056
20057    cx.assert_state_with_diff(
20058        "
20059            «aaa
20060          - bbb
20061            ccc
20062            ddd
20063
20064            ggg
20065            hhh
20066
20067
20068            lll
20069            mmm
20070          - nnn
20071          + NNN
20072
20073            qqq
20074            rrr
20075
20076            uuu
20077            111
20078            222
20079            333
20080
20081          + 666
20082            777
20083
20084            000
20085            !!!ˇ»"
20086            .unindent(),
20087    );
20088}
20089
20090#[gpui::test]
20091async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20092    init_test(cx, |_| {});
20093
20094    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20095    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20096
20097    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20098    let multi_buffer = cx.new(|cx| {
20099        let mut multibuffer = MultiBuffer::new(ReadWrite);
20100        multibuffer.push_excerpts(
20101            buffer.clone(),
20102            [
20103                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20104                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20105                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20106            ],
20107            cx,
20108        );
20109        multibuffer
20110    });
20111
20112    let editor =
20113        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20114    editor
20115        .update(cx, |editor, _window, cx| {
20116            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
20117            editor
20118                .buffer
20119                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20120        })
20121        .unwrap();
20122
20123    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20124    cx.run_until_parked();
20125
20126    cx.update_editor(|editor, window, cx| {
20127        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20128    });
20129    cx.executor().run_until_parked();
20130
20131    // When the start of a hunk coincides with the start of its excerpt,
20132    // the hunk is expanded. When the start of a hunk is earlier than
20133    // the start of its excerpt, the hunk is not expanded.
20134    cx.assert_state_with_diff(
20135        "
20136            ˇaaa
20137          - bbb
20138          + BBB
20139
20140          - ddd
20141          - eee
20142          + DDD
20143          + EEE
20144            fff
20145
20146            iii
20147        "
20148        .unindent(),
20149    );
20150}
20151
20152#[gpui::test]
20153async fn test_edits_around_expanded_insertion_hunks(
20154    executor: BackgroundExecutor,
20155    cx: &mut TestAppContext,
20156) {
20157    init_test(cx, |_| {});
20158
20159    let mut cx = EditorTestContext::new(cx).await;
20160
20161    let diff_base = r#"
20162        use some::mod1;
20163        use some::mod2;
20164
20165        const A: u32 = 42;
20166
20167        fn main() {
20168            println!("hello");
20169
20170            println!("world");
20171        }
20172        "#
20173    .unindent();
20174    executor.run_until_parked();
20175    cx.set_state(
20176        &r#"
20177        use some::mod1;
20178        use some::mod2;
20179
20180        const A: u32 = 42;
20181        const B: u32 = 42;
20182        const C: u32 = 42;
20183        ˇ
20184
20185        fn main() {
20186            println!("hello");
20187
20188            println!("world");
20189        }
20190        "#
20191        .unindent(),
20192    );
20193
20194    cx.set_head_text(&diff_base);
20195    executor.run_until_parked();
20196
20197    cx.update_editor(|editor, window, cx| {
20198        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20199    });
20200    executor.run_until_parked();
20201
20202    cx.assert_state_with_diff(
20203        r#"
20204        use some::mod1;
20205        use some::mod2;
20206
20207        const A: u32 = 42;
20208      + const B: u32 = 42;
20209      + const C: u32 = 42;
20210      + ˇ
20211
20212        fn main() {
20213            println!("hello");
20214
20215            println!("world");
20216        }
20217      "#
20218        .unindent(),
20219    );
20220
20221    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20222    executor.run_until_parked();
20223
20224    cx.assert_state_with_diff(
20225        r#"
20226        use some::mod1;
20227        use some::mod2;
20228
20229        const A: u32 = 42;
20230      + const B: u32 = 42;
20231      + const C: u32 = 42;
20232      + const D: u32 = 42;
20233      + ˇ
20234
20235        fn main() {
20236            println!("hello");
20237
20238            println!("world");
20239        }
20240      "#
20241        .unindent(),
20242    );
20243
20244    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20245    executor.run_until_parked();
20246
20247    cx.assert_state_with_diff(
20248        r#"
20249        use some::mod1;
20250        use some::mod2;
20251
20252        const A: u32 = 42;
20253      + const B: u32 = 42;
20254      + const C: u32 = 42;
20255      + const D: u32 = 42;
20256      + const E: u32 = 42;
20257      + ˇ
20258
20259        fn main() {
20260            println!("hello");
20261
20262            println!("world");
20263        }
20264      "#
20265        .unindent(),
20266    );
20267
20268    cx.update_editor(|editor, window, cx| {
20269        editor.delete_line(&DeleteLine, window, cx);
20270    });
20271    executor.run_until_parked();
20272
20273    cx.assert_state_with_diff(
20274        r#"
20275        use some::mod1;
20276        use some::mod2;
20277
20278        const A: u32 = 42;
20279      + const B: u32 = 42;
20280      + const C: u32 = 42;
20281      + const D: u32 = 42;
20282      + const E: u32 = 42;
20283        ˇ
20284        fn main() {
20285            println!("hello");
20286
20287            println!("world");
20288        }
20289      "#
20290        .unindent(),
20291    );
20292
20293    cx.update_editor(|editor, window, cx| {
20294        editor.move_up(&MoveUp, window, cx);
20295        editor.delete_line(&DeleteLine, window, cx);
20296        editor.move_up(&MoveUp, window, cx);
20297        editor.delete_line(&DeleteLine, window, cx);
20298        editor.move_up(&MoveUp, window, cx);
20299        editor.delete_line(&DeleteLine, window, cx);
20300    });
20301    executor.run_until_parked();
20302    cx.assert_state_with_diff(
20303        r#"
20304        use some::mod1;
20305        use some::mod2;
20306
20307        const A: u32 = 42;
20308      + const B: u32 = 42;
20309        ˇ
20310        fn main() {
20311            println!("hello");
20312
20313            println!("world");
20314        }
20315      "#
20316        .unindent(),
20317    );
20318
20319    cx.update_editor(|editor, window, cx| {
20320        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20321        editor.delete_line(&DeleteLine, window, cx);
20322    });
20323    executor.run_until_parked();
20324    cx.assert_state_with_diff(
20325        r#"
20326        ˇ
20327        fn main() {
20328            println!("hello");
20329
20330            println!("world");
20331        }
20332      "#
20333        .unindent(),
20334    );
20335}
20336
20337#[gpui::test]
20338async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20339    init_test(cx, |_| {});
20340
20341    let mut cx = EditorTestContext::new(cx).await;
20342    cx.set_head_text(indoc! { "
20343        one
20344        two
20345        three
20346        four
20347        five
20348        "
20349    });
20350    cx.set_state(indoc! { "
20351        one
20352        ˇthree
20353        five
20354    "});
20355    cx.run_until_parked();
20356    cx.update_editor(|editor, window, cx| {
20357        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20358    });
20359    cx.assert_state_with_diff(
20360        indoc! { "
20361        one
20362      - two
20363        ˇthree
20364      - four
20365        five
20366    "}
20367        .to_string(),
20368    );
20369    cx.update_editor(|editor, window, cx| {
20370        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20371    });
20372
20373    cx.assert_state_with_diff(
20374        indoc! { "
20375        one
20376        ˇthree
20377        five
20378    "}
20379        .to_string(),
20380    );
20381
20382    cx.set_state(indoc! { "
20383        one
20384        ˇTWO
20385        three
20386        four
20387        five
20388    "});
20389    cx.run_until_parked();
20390    cx.update_editor(|editor, window, cx| {
20391        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20392    });
20393
20394    cx.assert_state_with_diff(
20395        indoc! { "
20396            one
20397          - two
20398          + ˇTWO
20399            three
20400            four
20401            five
20402        "}
20403        .to_string(),
20404    );
20405    cx.update_editor(|editor, window, cx| {
20406        editor.move_up(&Default::default(), window, cx);
20407        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20408    });
20409    cx.assert_state_with_diff(
20410        indoc! { "
20411            one
20412            ˇTWO
20413            three
20414            four
20415            five
20416        "}
20417        .to_string(),
20418    );
20419}
20420
20421#[gpui::test]
20422async fn test_edits_around_expanded_deletion_hunks(
20423    executor: BackgroundExecutor,
20424    cx: &mut TestAppContext,
20425) {
20426    init_test(cx, |_| {});
20427
20428    let mut cx = EditorTestContext::new(cx).await;
20429
20430    let diff_base = r#"
20431        use some::mod1;
20432        use some::mod2;
20433
20434        const A: u32 = 42;
20435        const B: u32 = 42;
20436        const C: u32 = 42;
20437
20438
20439        fn main() {
20440            println!("hello");
20441
20442            println!("world");
20443        }
20444    "#
20445    .unindent();
20446    executor.run_until_parked();
20447    cx.set_state(
20448        &r#"
20449        use some::mod1;
20450        use some::mod2;
20451
20452        ˇconst B: u32 = 42;
20453        const C: u32 = 42;
20454
20455
20456        fn main() {
20457            println!("hello");
20458
20459            println!("world");
20460        }
20461        "#
20462        .unindent(),
20463    );
20464
20465    cx.set_head_text(&diff_base);
20466    executor.run_until_parked();
20467
20468    cx.update_editor(|editor, window, cx| {
20469        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20470    });
20471    executor.run_until_parked();
20472
20473    cx.assert_state_with_diff(
20474        r#"
20475        use some::mod1;
20476        use some::mod2;
20477
20478      - const A: u32 = 42;
20479        ˇconst B: u32 = 42;
20480        const C: u32 = 42;
20481
20482
20483        fn main() {
20484            println!("hello");
20485
20486            println!("world");
20487        }
20488      "#
20489        .unindent(),
20490    );
20491
20492    cx.update_editor(|editor, window, cx| {
20493        editor.delete_line(&DeleteLine, window, cx);
20494    });
20495    executor.run_until_parked();
20496    cx.assert_state_with_diff(
20497        r#"
20498        use some::mod1;
20499        use some::mod2;
20500
20501      - const A: u32 = 42;
20502      - const B: u32 = 42;
20503        ˇconst C: u32 = 42;
20504
20505
20506        fn main() {
20507            println!("hello");
20508
20509            println!("world");
20510        }
20511      "#
20512        .unindent(),
20513    );
20514
20515    cx.update_editor(|editor, window, cx| {
20516        editor.delete_line(&DeleteLine, window, cx);
20517    });
20518    executor.run_until_parked();
20519    cx.assert_state_with_diff(
20520        r#"
20521        use some::mod1;
20522        use some::mod2;
20523
20524      - const A: u32 = 42;
20525      - const B: u32 = 42;
20526      - const C: u32 = 42;
20527        ˇ
20528
20529        fn main() {
20530            println!("hello");
20531
20532            println!("world");
20533        }
20534      "#
20535        .unindent(),
20536    );
20537
20538    cx.update_editor(|editor, window, cx| {
20539        editor.handle_input("replacement", window, cx);
20540    });
20541    executor.run_until_parked();
20542    cx.assert_state_with_diff(
20543        r#"
20544        use some::mod1;
20545        use some::mod2;
20546
20547      - const A: u32 = 42;
20548      - const B: u32 = 42;
20549      - const C: u32 = 42;
20550      -
20551      + replacementˇ
20552
20553        fn main() {
20554            println!("hello");
20555
20556            println!("world");
20557        }
20558      "#
20559        .unindent(),
20560    );
20561}
20562
20563#[gpui::test]
20564async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20565    init_test(cx, |_| {});
20566
20567    let mut cx = EditorTestContext::new(cx).await;
20568
20569    let base_text = r#"
20570        one
20571        two
20572        three
20573        four
20574        five
20575    "#
20576    .unindent();
20577    executor.run_until_parked();
20578    cx.set_state(
20579        &r#"
20580        one
20581        two
20582        fˇour
20583        five
20584        "#
20585        .unindent(),
20586    );
20587
20588    cx.set_head_text(&base_text);
20589    executor.run_until_parked();
20590
20591    cx.update_editor(|editor, window, cx| {
20592        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20593    });
20594    executor.run_until_parked();
20595
20596    cx.assert_state_with_diff(
20597        r#"
20598          one
20599          two
20600        - three
20601          fˇour
20602          five
20603        "#
20604        .unindent(),
20605    );
20606
20607    cx.update_editor(|editor, window, cx| {
20608        editor.backspace(&Backspace, window, cx);
20609        editor.backspace(&Backspace, window, cx);
20610    });
20611    executor.run_until_parked();
20612    cx.assert_state_with_diff(
20613        r#"
20614          one
20615          two
20616        - threeˇ
20617        - four
20618        + our
20619          five
20620        "#
20621        .unindent(),
20622    );
20623}
20624
20625#[gpui::test]
20626async fn test_edit_after_expanded_modification_hunk(
20627    executor: BackgroundExecutor,
20628    cx: &mut TestAppContext,
20629) {
20630    init_test(cx, |_| {});
20631
20632    let mut cx = EditorTestContext::new(cx).await;
20633
20634    let diff_base = r#"
20635        use some::mod1;
20636        use some::mod2;
20637
20638        const A: u32 = 42;
20639        const B: u32 = 42;
20640        const C: u32 = 42;
20641        const D: u32 = 42;
20642
20643
20644        fn main() {
20645            println!("hello");
20646
20647            println!("world");
20648        }"#
20649    .unindent();
20650
20651    cx.set_state(
20652        &r#"
20653        use some::mod1;
20654        use some::mod2;
20655
20656        const A: u32 = 42;
20657        const B: u32 = 42;
20658        const C: u32 = 43ˇ
20659        const D: u32 = 42;
20660
20661
20662        fn main() {
20663            println!("hello");
20664
20665            println!("world");
20666        }"#
20667        .unindent(),
20668    );
20669
20670    cx.set_head_text(&diff_base);
20671    executor.run_until_parked();
20672    cx.update_editor(|editor, window, cx| {
20673        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20674    });
20675    executor.run_until_parked();
20676
20677    cx.assert_state_with_diff(
20678        r#"
20679        use some::mod1;
20680        use some::mod2;
20681
20682        const A: u32 = 42;
20683        const B: u32 = 42;
20684      - const C: u32 = 42;
20685      + const C: u32 = 43ˇ
20686        const D: u32 = 42;
20687
20688
20689        fn main() {
20690            println!("hello");
20691
20692            println!("world");
20693        }"#
20694        .unindent(),
20695    );
20696
20697    cx.update_editor(|editor, window, cx| {
20698        editor.handle_input("\nnew_line\n", window, cx);
20699    });
20700    executor.run_until_parked();
20701
20702    cx.assert_state_with_diff(
20703        r#"
20704        use some::mod1;
20705        use some::mod2;
20706
20707        const A: u32 = 42;
20708        const B: u32 = 42;
20709      - const C: u32 = 42;
20710      + const C: u32 = 43
20711      + new_line
20712      + ˇ
20713        const D: u32 = 42;
20714
20715
20716        fn main() {
20717            println!("hello");
20718
20719            println!("world");
20720        }"#
20721        .unindent(),
20722    );
20723}
20724
20725#[gpui::test]
20726async fn test_stage_and_unstage_added_file_hunk(
20727    executor: BackgroundExecutor,
20728    cx: &mut TestAppContext,
20729) {
20730    init_test(cx, |_| {});
20731
20732    let mut cx = EditorTestContext::new(cx).await;
20733    cx.update_editor(|editor, _, cx| {
20734        editor.set_expand_all_diff_hunks(cx);
20735    });
20736
20737    let working_copy = r#"
20738            ˇfn main() {
20739                println!("hello, world!");
20740            }
20741        "#
20742    .unindent();
20743
20744    cx.set_state(&working_copy);
20745    executor.run_until_parked();
20746
20747    cx.assert_state_with_diff(
20748        r#"
20749            + ˇfn main() {
20750            +     println!("hello, world!");
20751            + }
20752        "#
20753        .unindent(),
20754    );
20755    cx.assert_index_text(None);
20756
20757    cx.update_editor(|editor, window, cx| {
20758        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20759    });
20760    executor.run_until_parked();
20761    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20762    cx.assert_state_with_diff(
20763        r#"
20764            + ˇfn main() {
20765            +     println!("hello, world!");
20766            + }
20767        "#
20768        .unindent(),
20769    );
20770
20771    cx.update_editor(|editor, window, cx| {
20772        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20773    });
20774    executor.run_until_parked();
20775    cx.assert_index_text(None);
20776}
20777
20778async fn setup_indent_guides_editor(
20779    text: &str,
20780    cx: &mut TestAppContext,
20781) -> (BufferId, EditorTestContext) {
20782    init_test(cx, |_| {});
20783
20784    let mut cx = EditorTestContext::new(cx).await;
20785
20786    let buffer_id = cx.update_editor(|editor, window, cx| {
20787        editor.set_text(text, window, cx);
20788        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20789
20790        buffer_ids[0]
20791    });
20792
20793    (buffer_id, cx)
20794}
20795
20796fn assert_indent_guides(
20797    range: Range<u32>,
20798    expected: Vec<IndentGuide>,
20799    active_indices: Option<Vec<usize>>,
20800    cx: &mut EditorTestContext,
20801) {
20802    let indent_guides = cx.update_editor(|editor, window, cx| {
20803        let snapshot = editor.snapshot(window, cx).display_snapshot;
20804        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20805            editor,
20806            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20807            true,
20808            &snapshot,
20809            cx,
20810        );
20811
20812        indent_guides.sort_by(|a, b| {
20813            a.depth.cmp(&b.depth).then(
20814                a.start_row
20815                    .cmp(&b.start_row)
20816                    .then(a.end_row.cmp(&b.end_row)),
20817            )
20818        });
20819        indent_guides
20820    });
20821
20822    if let Some(expected) = active_indices {
20823        let active_indices = cx.update_editor(|editor, window, cx| {
20824            let snapshot = editor.snapshot(window, cx).display_snapshot;
20825            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20826        });
20827
20828        assert_eq!(
20829            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20830            expected,
20831            "Active indent guide indices do not match"
20832        );
20833    }
20834
20835    assert_eq!(indent_guides, expected, "Indent guides do not match");
20836}
20837
20838fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20839    IndentGuide {
20840        buffer_id,
20841        start_row: MultiBufferRow(start_row),
20842        end_row: MultiBufferRow(end_row),
20843        depth,
20844        tab_size: 4,
20845        settings: IndentGuideSettings {
20846            enabled: true,
20847            line_width: 1,
20848            active_line_width: 1,
20849            coloring: IndentGuideColoring::default(),
20850            background_coloring: IndentGuideBackgroundColoring::default(),
20851        },
20852    }
20853}
20854
20855#[gpui::test]
20856async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20857    let (buffer_id, mut cx) = setup_indent_guides_editor(
20858        &"
20859        fn main() {
20860            let a = 1;
20861        }"
20862        .unindent(),
20863        cx,
20864    )
20865    .await;
20866
20867    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20868}
20869
20870#[gpui::test]
20871async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20872    let (buffer_id, mut cx) = setup_indent_guides_editor(
20873        &"
20874        fn main() {
20875            let a = 1;
20876            let b = 2;
20877        }"
20878        .unindent(),
20879        cx,
20880    )
20881    .await;
20882
20883    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20884}
20885
20886#[gpui::test]
20887async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20888    let (buffer_id, mut cx) = setup_indent_guides_editor(
20889        &"
20890        fn main() {
20891            let a = 1;
20892            if a == 3 {
20893                let b = 2;
20894            } else {
20895                let c = 3;
20896            }
20897        }"
20898        .unindent(),
20899        cx,
20900    )
20901    .await;
20902
20903    assert_indent_guides(
20904        0..8,
20905        vec![
20906            indent_guide(buffer_id, 1, 6, 0),
20907            indent_guide(buffer_id, 3, 3, 1),
20908            indent_guide(buffer_id, 5, 5, 1),
20909        ],
20910        None,
20911        &mut cx,
20912    );
20913}
20914
20915#[gpui::test]
20916async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20917    let (buffer_id, mut cx) = setup_indent_guides_editor(
20918        &"
20919        fn main() {
20920            let a = 1;
20921                let b = 2;
20922            let c = 3;
20923        }"
20924        .unindent(),
20925        cx,
20926    )
20927    .await;
20928
20929    assert_indent_guides(
20930        0..5,
20931        vec![
20932            indent_guide(buffer_id, 1, 3, 0),
20933            indent_guide(buffer_id, 2, 2, 1),
20934        ],
20935        None,
20936        &mut cx,
20937    );
20938}
20939
20940#[gpui::test]
20941async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20942    let (buffer_id, mut cx) = setup_indent_guides_editor(
20943        &"
20944        fn main() {
20945            let a = 1;
20946
20947            let c = 3;
20948        }"
20949        .unindent(),
20950        cx,
20951    )
20952    .await;
20953
20954    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20955}
20956
20957#[gpui::test]
20958async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20959    let (buffer_id, mut cx) = setup_indent_guides_editor(
20960        &"
20961        fn main() {
20962            let a = 1;
20963
20964            let c = 3;
20965
20966            if a == 3 {
20967                let b = 2;
20968            } else {
20969                let c = 3;
20970            }
20971        }"
20972        .unindent(),
20973        cx,
20974    )
20975    .await;
20976
20977    assert_indent_guides(
20978        0..11,
20979        vec![
20980            indent_guide(buffer_id, 1, 9, 0),
20981            indent_guide(buffer_id, 6, 6, 1),
20982            indent_guide(buffer_id, 8, 8, 1),
20983        ],
20984        None,
20985        &mut cx,
20986    );
20987}
20988
20989#[gpui::test]
20990async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20991    let (buffer_id, mut cx) = setup_indent_guides_editor(
20992        &"
20993        fn main() {
20994            let a = 1;
20995
20996            let c = 3;
20997
20998            if a == 3 {
20999                let b = 2;
21000            } else {
21001                let c = 3;
21002            }
21003        }"
21004        .unindent(),
21005        cx,
21006    )
21007    .await;
21008
21009    assert_indent_guides(
21010        1..11,
21011        vec![
21012            indent_guide(buffer_id, 1, 9, 0),
21013            indent_guide(buffer_id, 6, 6, 1),
21014            indent_guide(buffer_id, 8, 8, 1),
21015        ],
21016        None,
21017        &mut cx,
21018    );
21019}
21020
21021#[gpui::test]
21022async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21023    let (buffer_id, mut cx) = setup_indent_guides_editor(
21024        &"
21025        fn main() {
21026            let a = 1;
21027
21028            let c = 3;
21029
21030            if a == 3 {
21031                let b = 2;
21032            } else {
21033                let c = 3;
21034            }
21035        }"
21036        .unindent(),
21037        cx,
21038    )
21039    .await;
21040
21041    assert_indent_guides(
21042        1..10,
21043        vec![
21044            indent_guide(buffer_id, 1, 9, 0),
21045            indent_guide(buffer_id, 6, 6, 1),
21046            indent_guide(buffer_id, 8, 8, 1),
21047        ],
21048        None,
21049        &mut cx,
21050    );
21051}
21052
21053#[gpui::test]
21054async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21055    let (buffer_id, mut cx) = setup_indent_guides_editor(
21056        &"
21057        fn main() {
21058            if a {
21059                b(
21060                    c,
21061                    d,
21062                )
21063            } else {
21064                e(
21065                    f
21066                )
21067            }
21068        }"
21069        .unindent(),
21070        cx,
21071    )
21072    .await;
21073
21074    assert_indent_guides(
21075        0..11,
21076        vec![
21077            indent_guide(buffer_id, 1, 10, 0),
21078            indent_guide(buffer_id, 2, 5, 1),
21079            indent_guide(buffer_id, 7, 9, 1),
21080            indent_guide(buffer_id, 3, 4, 2),
21081            indent_guide(buffer_id, 8, 8, 2),
21082        ],
21083        None,
21084        &mut cx,
21085    );
21086
21087    cx.update_editor(|editor, window, cx| {
21088        editor.fold_at(MultiBufferRow(2), window, cx);
21089        assert_eq!(
21090            editor.display_text(cx),
21091            "
21092            fn main() {
21093                if a {
21094                    b(⋯
21095                    )
21096                } else {
21097                    e(
21098                        f
21099                    )
21100                }
21101            }"
21102            .unindent()
21103        );
21104    });
21105
21106    assert_indent_guides(
21107        0..11,
21108        vec![
21109            indent_guide(buffer_id, 1, 10, 0),
21110            indent_guide(buffer_id, 2, 5, 1),
21111            indent_guide(buffer_id, 7, 9, 1),
21112            indent_guide(buffer_id, 8, 8, 2),
21113        ],
21114        None,
21115        &mut cx,
21116    );
21117}
21118
21119#[gpui::test]
21120async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21121    let (buffer_id, mut cx) = setup_indent_guides_editor(
21122        &"
21123        block1
21124            block2
21125                block3
21126                    block4
21127            block2
21128        block1
21129        block1"
21130            .unindent(),
21131        cx,
21132    )
21133    .await;
21134
21135    assert_indent_guides(
21136        1..10,
21137        vec![
21138            indent_guide(buffer_id, 1, 4, 0),
21139            indent_guide(buffer_id, 2, 3, 1),
21140            indent_guide(buffer_id, 3, 3, 2),
21141        ],
21142        None,
21143        &mut cx,
21144    );
21145}
21146
21147#[gpui::test]
21148async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21149    let (buffer_id, mut cx) = setup_indent_guides_editor(
21150        &"
21151        block1
21152            block2
21153                block3
21154
21155        block1
21156        block1"
21157            .unindent(),
21158        cx,
21159    )
21160    .await;
21161
21162    assert_indent_guides(
21163        0..6,
21164        vec![
21165            indent_guide(buffer_id, 1, 2, 0),
21166            indent_guide(buffer_id, 2, 2, 1),
21167        ],
21168        None,
21169        &mut cx,
21170    );
21171}
21172
21173#[gpui::test]
21174async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21175    let (buffer_id, mut cx) = setup_indent_guides_editor(
21176        &"
21177        function component() {
21178        \treturn (
21179        \t\t\t
21180        \t\t<div>
21181        \t\t\t<abc></abc>
21182        \t\t</div>
21183        \t)
21184        }"
21185        .unindent(),
21186        cx,
21187    )
21188    .await;
21189
21190    assert_indent_guides(
21191        0..8,
21192        vec![
21193            indent_guide(buffer_id, 1, 6, 0),
21194            indent_guide(buffer_id, 2, 5, 1),
21195            indent_guide(buffer_id, 4, 4, 2),
21196        ],
21197        None,
21198        &mut cx,
21199    );
21200}
21201
21202#[gpui::test]
21203async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21204    let (buffer_id, mut cx) = setup_indent_guides_editor(
21205        &"
21206        function component() {
21207        \treturn (
21208        \t
21209        \t\t<div>
21210        \t\t\t<abc></abc>
21211        \t\t</div>
21212        \t)
21213        }"
21214        .unindent(),
21215        cx,
21216    )
21217    .await;
21218
21219    assert_indent_guides(
21220        0..8,
21221        vec![
21222            indent_guide(buffer_id, 1, 6, 0),
21223            indent_guide(buffer_id, 2, 5, 1),
21224            indent_guide(buffer_id, 4, 4, 2),
21225        ],
21226        None,
21227        &mut cx,
21228    );
21229}
21230
21231#[gpui::test]
21232async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21233    let (buffer_id, mut cx) = setup_indent_guides_editor(
21234        &"
21235        block1
21236
21237
21238
21239            block2
21240        "
21241        .unindent(),
21242        cx,
21243    )
21244    .await;
21245
21246    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21247}
21248
21249#[gpui::test]
21250async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21251    let (buffer_id, mut cx) = setup_indent_guides_editor(
21252        &"
21253        def a:
21254        \tb = 3
21255        \tif True:
21256        \t\tc = 4
21257        \t\td = 5
21258        \tprint(b)
21259        "
21260        .unindent(),
21261        cx,
21262    )
21263    .await;
21264
21265    assert_indent_guides(
21266        0..6,
21267        vec![
21268            indent_guide(buffer_id, 1, 5, 0),
21269            indent_guide(buffer_id, 3, 4, 1),
21270        ],
21271        None,
21272        &mut cx,
21273    );
21274}
21275
21276#[gpui::test]
21277async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21278    let (buffer_id, mut cx) = setup_indent_guides_editor(
21279        &"
21280    fn main() {
21281        let a = 1;
21282    }"
21283        .unindent(),
21284        cx,
21285    )
21286    .await;
21287
21288    cx.update_editor(|editor, window, cx| {
21289        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21290            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21291        });
21292    });
21293
21294    assert_indent_guides(
21295        0..3,
21296        vec![indent_guide(buffer_id, 1, 1, 0)],
21297        Some(vec![0]),
21298        &mut cx,
21299    );
21300}
21301
21302#[gpui::test]
21303async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21304    let (buffer_id, mut cx) = setup_indent_guides_editor(
21305        &"
21306    fn main() {
21307        if 1 == 2 {
21308            let a = 1;
21309        }
21310    }"
21311        .unindent(),
21312        cx,
21313    )
21314    .await;
21315
21316    cx.update_editor(|editor, window, cx| {
21317        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21318            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21319        });
21320    });
21321
21322    assert_indent_guides(
21323        0..4,
21324        vec![
21325            indent_guide(buffer_id, 1, 3, 0),
21326            indent_guide(buffer_id, 2, 2, 1),
21327        ],
21328        Some(vec![1]),
21329        &mut cx,
21330    );
21331
21332    cx.update_editor(|editor, window, cx| {
21333        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21334            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21335        });
21336    });
21337
21338    assert_indent_guides(
21339        0..4,
21340        vec![
21341            indent_guide(buffer_id, 1, 3, 0),
21342            indent_guide(buffer_id, 2, 2, 1),
21343        ],
21344        Some(vec![1]),
21345        &mut cx,
21346    );
21347
21348    cx.update_editor(|editor, window, cx| {
21349        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21350            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21351        });
21352    });
21353
21354    assert_indent_guides(
21355        0..4,
21356        vec![
21357            indent_guide(buffer_id, 1, 3, 0),
21358            indent_guide(buffer_id, 2, 2, 1),
21359        ],
21360        Some(vec![0]),
21361        &mut cx,
21362    );
21363}
21364
21365#[gpui::test]
21366async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21367    let (buffer_id, mut cx) = setup_indent_guides_editor(
21368        &"
21369    fn main() {
21370        let a = 1;
21371
21372        let b = 2;
21373    }"
21374        .unindent(),
21375        cx,
21376    )
21377    .await;
21378
21379    cx.update_editor(|editor, window, cx| {
21380        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21381            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21382        });
21383    });
21384
21385    assert_indent_guides(
21386        0..5,
21387        vec![indent_guide(buffer_id, 1, 3, 0)],
21388        Some(vec![0]),
21389        &mut cx,
21390    );
21391}
21392
21393#[gpui::test]
21394async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21395    let (buffer_id, mut cx) = setup_indent_guides_editor(
21396        &"
21397    def m:
21398        a = 1
21399        pass"
21400            .unindent(),
21401        cx,
21402    )
21403    .await;
21404
21405    cx.update_editor(|editor, window, cx| {
21406        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21407            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21408        });
21409    });
21410
21411    assert_indent_guides(
21412        0..3,
21413        vec![indent_guide(buffer_id, 1, 2, 0)],
21414        Some(vec![0]),
21415        &mut cx,
21416    );
21417}
21418
21419#[gpui::test]
21420async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21421    init_test(cx, |_| {});
21422    let mut cx = EditorTestContext::new(cx).await;
21423    let text = indoc! {
21424        "
21425        impl A {
21426            fn b() {
21427                0;
21428                3;
21429                5;
21430                6;
21431                7;
21432            }
21433        }
21434        "
21435    };
21436    let base_text = indoc! {
21437        "
21438        impl A {
21439            fn b() {
21440                0;
21441                1;
21442                2;
21443                3;
21444                4;
21445            }
21446            fn c() {
21447                5;
21448                6;
21449                7;
21450            }
21451        }
21452        "
21453    };
21454
21455    cx.update_editor(|editor, window, cx| {
21456        editor.set_text(text, window, cx);
21457
21458        editor.buffer().update(cx, |multibuffer, cx| {
21459            let buffer = multibuffer.as_singleton().unwrap();
21460            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21461
21462            multibuffer.set_all_diff_hunks_expanded(cx);
21463            multibuffer.add_diff(diff, cx);
21464
21465            buffer.read(cx).remote_id()
21466        })
21467    });
21468    cx.run_until_parked();
21469
21470    cx.assert_state_with_diff(
21471        indoc! { "
21472          impl A {
21473              fn b() {
21474                  0;
21475        -         1;
21476        -         2;
21477                  3;
21478        -         4;
21479        -     }
21480        -     fn c() {
21481                  5;
21482                  6;
21483                  7;
21484              }
21485          }
21486          ˇ"
21487        }
21488        .to_string(),
21489    );
21490
21491    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21492        editor
21493            .snapshot(window, cx)
21494            .buffer_snapshot()
21495            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21496            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21497            .collect::<Vec<_>>()
21498    });
21499    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21500    assert_eq!(
21501        actual_guides,
21502        vec![
21503            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21504            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21505            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21506        ]
21507    );
21508}
21509
21510#[gpui::test]
21511async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21512    init_test(cx, |_| {});
21513    let mut cx = EditorTestContext::new(cx).await;
21514
21515    let diff_base = r#"
21516        a
21517        b
21518        c
21519        "#
21520    .unindent();
21521
21522    cx.set_state(
21523        &r#"
21524        ˇA
21525        b
21526        C
21527        "#
21528        .unindent(),
21529    );
21530    cx.set_head_text(&diff_base);
21531    cx.update_editor(|editor, window, cx| {
21532        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21533    });
21534    executor.run_until_parked();
21535
21536    let both_hunks_expanded = r#"
21537        - a
21538        + ˇA
21539          b
21540        - c
21541        + C
21542        "#
21543    .unindent();
21544
21545    cx.assert_state_with_diff(both_hunks_expanded.clone());
21546
21547    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21548        let snapshot = editor.snapshot(window, cx);
21549        let hunks = editor
21550            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21551            .collect::<Vec<_>>();
21552        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21553        let buffer_id = hunks[0].buffer_id;
21554        hunks
21555            .into_iter()
21556            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21557            .collect::<Vec<_>>()
21558    });
21559    assert_eq!(hunk_ranges.len(), 2);
21560
21561    cx.update_editor(|editor, _, cx| {
21562        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21563    });
21564    executor.run_until_parked();
21565
21566    let second_hunk_expanded = r#"
21567          ˇA
21568          b
21569        - c
21570        + C
21571        "#
21572    .unindent();
21573
21574    cx.assert_state_with_diff(second_hunk_expanded);
21575
21576    cx.update_editor(|editor, _, cx| {
21577        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21578    });
21579    executor.run_until_parked();
21580
21581    cx.assert_state_with_diff(both_hunks_expanded.clone());
21582
21583    cx.update_editor(|editor, _, cx| {
21584        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21585    });
21586    executor.run_until_parked();
21587
21588    let first_hunk_expanded = r#"
21589        - a
21590        + ˇA
21591          b
21592          C
21593        "#
21594    .unindent();
21595
21596    cx.assert_state_with_diff(first_hunk_expanded);
21597
21598    cx.update_editor(|editor, _, cx| {
21599        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21600    });
21601    executor.run_until_parked();
21602
21603    cx.assert_state_with_diff(both_hunks_expanded);
21604
21605    cx.set_state(
21606        &r#"
21607        ˇA
21608        b
21609        "#
21610        .unindent(),
21611    );
21612    cx.run_until_parked();
21613
21614    // TODO this cursor position seems bad
21615    cx.assert_state_with_diff(
21616        r#"
21617        - ˇa
21618        + A
21619          b
21620        "#
21621        .unindent(),
21622    );
21623
21624    cx.update_editor(|editor, window, cx| {
21625        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21626    });
21627
21628    cx.assert_state_with_diff(
21629        r#"
21630            - ˇa
21631            + A
21632              b
21633            - c
21634            "#
21635        .unindent(),
21636    );
21637
21638    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21639        let snapshot = editor.snapshot(window, cx);
21640        let hunks = editor
21641            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21642            .collect::<Vec<_>>();
21643        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21644        let buffer_id = hunks[0].buffer_id;
21645        hunks
21646            .into_iter()
21647            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21648            .collect::<Vec<_>>()
21649    });
21650    assert_eq!(hunk_ranges.len(), 2);
21651
21652    cx.update_editor(|editor, _, cx| {
21653        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21654    });
21655    executor.run_until_parked();
21656
21657    cx.assert_state_with_diff(
21658        r#"
21659        - ˇa
21660        + A
21661          b
21662        "#
21663        .unindent(),
21664    );
21665}
21666
21667#[gpui::test]
21668async fn test_toggle_deletion_hunk_at_start_of_file(
21669    executor: BackgroundExecutor,
21670    cx: &mut TestAppContext,
21671) {
21672    init_test(cx, |_| {});
21673    let mut cx = EditorTestContext::new(cx).await;
21674
21675    let diff_base = r#"
21676        a
21677        b
21678        c
21679        "#
21680    .unindent();
21681
21682    cx.set_state(
21683        &r#"
21684        ˇb
21685        c
21686        "#
21687        .unindent(),
21688    );
21689    cx.set_head_text(&diff_base);
21690    cx.update_editor(|editor, window, cx| {
21691        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21692    });
21693    executor.run_until_parked();
21694
21695    let hunk_expanded = r#"
21696        - a
21697          ˇb
21698          c
21699        "#
21700    .unindent();
21701
21702    cx.assert_state_with_diff(hunk_expanded.clone());
21703
21704    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21705        let snapshot = editor.snapshot(window, cx);
21706        let hunks = editor
21707            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21708            .collect::<Vec<_>>();
21709        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21710        let buffer_id = hunks[0].buffer_id;
21711        hunks
21712            .into_iter()
21713            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21714            .collect::<Vec<_>>()
21715    });
21716    assert_eq!(hunk_ranges.len(), 1);
21717
21718    cx.update_editor(|editor, _, cx| {
21719        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21720    });
21721    executor.run_until_parked();
21722
21723    let hunk_collapsed = r#"
21724          ˇb
21725          c
21726        "#
21727    .unindent();
21728
21729    cx.assert_state_with_diff(hunk_collapsed);
21730
21731    cx.update_editor(|editor, _, cx| {
21732        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21733    });
21734    executor.run_until_parked();
21735
21736    cx.assert_state_with_diff(hunk_expanded);
21737}
21738
21739#[gpui::test]
21740async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21741    init_test(cx, |_| {});
21742
21743    let fs = FakeFs::new(cx.executor());
21744    fs.insert_tree(
21745        path!("/test"),
21746        json!({
21747            ".git": {},
21748            "file-1": "ONE\n",
21749            "file-2": "TWO\n",
21750            "file-3": "THREE\n",
21751        }),
21752    )
21753    .await;
21754
21755    fs.set_head_for_repo(
21756        path!("/test/.git").as_ref(),
21757        &[
21758            ("file-1", "one\n".into()),
21759            ("file-2", "two\n".into()),
21760            ("file-3", "three\n".into()),
21761        ],
21762        "deadbeef",
21763    );
21764
21765    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21766    let mut buffers = vec![];
21767    for i in 1..=3 {
21768        let buffer = project
21769            .update(cx, |project, cx| {
21770                let path = format!(path!("/test/file-{}"), i);
21771                project.open_local_buffer(path, cx)
21772            })
21773            .await
21774            .unwrap();
21775        buffers.push(buffer);
21776    }
21777
21778    let multibuffer = cx.new(|cx| {
21779        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21780        multibuffer.set_all_diff_hunks_expanded(cx);
21781        for buffer in &buffers {
21782            let snapshot = buffer.read(cx).snapshot();
21783            multibuffer.set_excerpts_for_path(
21784                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21785                buffer.clone(),
21786                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21787                2,
21788                cx,
21789            );
21790        }
21791        multibuffer
21792    });
21793
21794    let editor = cx.add_window(|window, cx| {
21795        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21796    });
21797    cx.run_until_parked();
21798
21799    let snapshot = editor
21800        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21801        .unwrap();
21802    let hunks = snapshot
21803        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21804        .map(|hunk| match hunk {
21805            DisplayDiffHunk::Unfolded {
21806                display_row_range, ..
21807            } => display_row_range,
21808            DisplayDiffHunk::Folded { .. } => unreachable!(),
21809        })
21810        .collect::<Vec<_>>();
21811    assert_eq!(
21812        hunks,
21813        [
21814            DisplayRow(2)..DisplayRow(4),
21815            DisplayRow(7)..DisplayRow(9),
21816            DisplayRow(12)..DisplayRow(14),
21817        ]
21818    );
21819}
21820
21821#[gpui::test]
21822async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21823    init_test(cx, |_| {});
21824
21825    let mut cx = EditorTestContext::new(cx).await;
21826    cx.set_head_text(indoc! { "
21827        one
21828        two
21829        three
21830        four
21831        five
21832        "
21833    });
21834    cx.set_index_text(indoc! { "
21835        one
21836        two
21837        three
21838        four
21839        five
21840        "
21841    });
21842    cx.set_state(indoc! {"
21843        one
21844        TWO
21845        ˇTHREE
21846        FOUR
21847        five
21848    "});
21849    cx.run_until_parked();
21850    cx.update_editor(|editor, window, cx| {
21851        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21852    });
21853    cx.run_until_parked();
21854    cx.assert_index_text(Some(indoc! {"
21855        one
21856        TWO
21857        THREE
21858        FOUR
21859        five
21860    "}));
21861    cx.set_state(indoc! { "
21862        one
21863        TWO
21864        ˇTHREE-HUNDRED
21865        FOUR
21866        five
21867    "});
21868    cx.run_until_parked();
21869    cx.update_editor(|editor, window, cx| {
21870        let snapshot = editor.snapshot(window, cx);
21871        let hunks = editor
21872            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21873            .collect::<Vec<_>>();
21874        assert_eq!(hunks.len(), 1);
21875        assert_eq!(
21876            hunks[0].status(),
21877            DiffHunkStatus {
21878                kind: DiffHunkStatusKind::Modified,
21879                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21880            }
21881        );
21882
21883        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21884    });
21885    cx.run_until_parked();
21886    cx.assert_index_text(Some(indoc! {"
21887        one
21888        TWO
21889        THREE-HUNDRED
21890        FOUR
21891        five
21892    "}));
21893}
21894
21895#[gpui::test]
21896fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21897    init_test(cx, |_| {});
21898
21899    let editor = cx.add_window(|window, cx| {
21900        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21901        build_editor(buffer, window, cx)
21902    });
21903
21904    let render_args = Arc::new(Mutex::new(None));
21905    let snapshot = editor
21906        .update(cx, |editor, window, cx| {
21907            let snapshot = editor.buffer().read(cx).snapshot(cx);
21908            let range =
21909                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21910
21911            struct RenderArgs {
21912                row: MultiBufferRow,
21913                folded: bool,
21914                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21915            }
21916
21917            let crease = Crease::inline(
21918                range,
21919                FoldPlaceholder::test(),
21920                {
21921                    let toggle_callback = render_args.clone();
21922                    move |row, folded, callback, _window, _cx| {
21923                        *toggle_callback.lock() = Some(RenderArgs {
21924                            row,
21925                            folded,
21926                            callback,
21927                        });
21928                        div()
21929                    }
21930                },
21931                |_row, _folded, _window, _cx| div(),
21932            );
21933
21934            editor.insert_creases(Some(crease), cx);
21935            let snapshot = editor.snapshot(window, cx);
21936            let _div =
21937                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21938            snapshot
21939        })
21940        .unwrap();
21941
21942    let render_args = render_args.lock().take().unwrap();
21943    assert_eq!(render_args.row, MultiBufferRow(1));
21944    assert!(!render_args.folded);
21945    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21946
21947    cx.update_window(*editor, |_, window, cx| {
21948        (render_args.callback)(true, window, cx)
21949    })
21950    .unwrap();
21951    let snapshot = editor
21952        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21953        .unwrap();
21954    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21955
21956    cx.update_window(*editor, |_, window, cx| {
21957        (render_args.callback)(false, window, cx)
21958    })
21959    .unwrap();
21960    let snapshot = editor
21961        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21962        .unwrap();
21963    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21964}
21965
21966#[gpui::test]
21967async fn test_input_text(cx: &mut TestAppContext) {
21968    init_test(cx, |_| {});
21969    let mut cx = EditorTestContext::new(cx).await;
21970
21971    cx.set_state(
21972        &r#"ˇone
21973        two
21974
21975        three
21976        fourˇ
21977        five
21978
21979        siˇx"#
21980            .unindent(),
21981    );
21982
21983    cx.dispatch_action(HandleInput(String::new()));
21984    cx.assert_editor_state(
21985        &r#"ˇone
21986        two
21987
21988        three
21989        fourˇ
21990        five
21991
21992        siˇx"#
21993            .unindent(),
21994    );
21995
21996    cx.dispatch_action(HandleInput("AAAA".to_string()));
21997    cx.assert_editor_state(
21998        &r#"AAAAˇone
21999        two
22000
22001        three
22002        fourAAAAˇ
22003        five
22004
22005        siAAAAˇx"#
22006            .unindent(),
22007    );
22008}
22009
22010#[gpui::test]
22011async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22012    init_test(cx, |_| {});
22013
22014    let mut cx = EditorTestContext::new(cx).await;
22015    cx.set_state(
22016        r#"let foo = 1;
22017let foo = 2;
22018let foo = 3;
22019let fooˇ = 4;
22020let foo = 5;
22021let foo = 6;
22022let foo = 7;
22023let foo = 8;
22024let foo = 9;
22025let foo = 10;
22026let foo = 11;
22027let foo = 12;
22028let foo = 13;
22029let foo = 14;
22030let foo = 15;"#,
22031    );
22032
22033    cx.update_editor(|e, window, cx| {
22034        assert_eq!(
22035            e.next_scroll_position,
22036            NextScrollCursorCenterTopBottom::Center,
22037            "Default next scroll direction is center",
22038        );
22039
22040        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22041        assert_eq!(
22042            e.next_scroll_position,
22043            NextScrollCursorCenterTopBottom::Top,
22044            "After center, next scroll direction should be top",
22045        );
22046
22047        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22048        assert_eq!(
22049            e.next_scroll_position,
22050            NextScrollCursorCenterTopBottom::Bottom,
22051            "After top, next scroll direction should be bottom",
22052        );
22053
22054        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22055        assert_eq!(
22056            e.next_scroll_position,
22057            NextScrollCursorCenterTopBottom::Center,
22058            "After bottom, scrolling should start over",
22059        );
22060
22061        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22062        assert_eq!(
22063            e.next_scroll_position,
22064            NextScrollCursorCenterTopBottom::Top,
22065            "Scrolling continues if retriggered fast enough"
22066        );
22067    });
22068
22069    cx.executor()
22070        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22071    cx.executor().run_until_parked();
22072    cx.update_editor(|e, _, _| {
22073        assert_eq!(
22074            e.next_scroll_position,
22075            NextScrollCursorCenterTopBottom::Center,
22076            "If scrolling is not triggered fast enough, it should reset"
22077        );
22078    });
22079}
22080
22081#[gpui::test]
22082async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22083    init_test(cx, |_| {});
22084    let mut cx = EditorLspTestContext::new_rust(
22085        lsp::ServerCapabilities {
22086            definition_provider: Some(lsp::OneOf::Left(true)),
22087            references_provider: Some(lsp::OneOf::Left(true)),
22088            ..lsp::ServerCapabilities::default()
22089        },
22090        cx,
22091    )
22092    .await;
22093
22094    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22095        let go_to_definition = cx
22096            .lsp
22097            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22098                move |params, _| async move {
22099                    if empty_go_to_definition {
22100                        Ok(None)
22101                    } else {
22102                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22103                            uri: params.text_document_position_params.text_document.uri,
22104                            range: lsp::Range::new(
22105                                lsp::Position::new(4, 3),
22106                                lsp::Position::new(4, 6),
22107                            ),
22108                        })))
22109                    }
22110                },
22111            );
22112        let references = cx
22113            .lsp
22114            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22115                Ok(Some(vec![lsp::Location {
22116                    uri: params.text_document_position.text_document.uri,
22117                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22118                }]))
22119            });
22120        (go_to_definition, references)
22121    };
22122
22123    cx.set_state(
22124        &r#"fn one() {
22125            let mut a = ˇtwo();
22126        }
22127
22128        fn two() {}"#
22129            .unindent(),
22130    );
22131    set_up_lsp_handlers(false, &mut cx);
22132    let navigated = cx
22133        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22134        .await
22135        .expect("Failed to navigate to definition");
22136    assert_eq!(
22137        navigated,
22138        Navigated::Yes,
22139        "Should have navigated to definition from the GetDefinition response"
22140    );
22141    cx.assert_editor_state(
22142        &r#"fn one() {
22143            let mut a = two();
22144        }
22145
22146        fn «twoˇ»() {}"#
22147            .unindent(),
22148    );
22149
22150    let editors = cx.update_workspace(|workspace, _, cx| {
22151        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22152    });
22153    cx.update_editor(|_, _, test_editor_cx| {
22154        assert_eq!(
22155            editors.len(),
22156            1,
22157            "Initially, only one, test, editor should be open in the workspace"
22158        );
22159        assert_eq!(
22160            test_editor_cx.entity(),
22161            editors.last().expect("Asserted len is 1").clone()
22162        );
22163    });
22164
22165    set_up_lsp_handlers(true, &mut cx);
22166    let navigated = cx
22167        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22168        .await
22169        .expect("Failed to navigate to lookup references");
22170    assert_eq!(
22171        navigated,
22172        Navigated::Yes,
22173        "Should have navigated to references as a fallback after empty GoToDefinition response"
22174    );
22175    // We should not change the selections in the existing file,
22176    // if opening another milti buffer with the references
22177    cx.assert_editor_state(
22178        &r#"fn one() {
22179            let mut a = two();
22180        }
22181
22182        fn «twoˇ»() {}"#
22183            .unindent(),
22184    );
22185    let editors = cx.update_workspace(|workspace, _, cx| {
22186        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22187    });
22188    cx.update_editor(|_, _, test_editor_cx| {
22189        assert_eq!(
22190            editors.len(),
22191            2,
22192            "After falling back to references search, we open a new editor with the results"
22193        );
22194        let references_fallback_text = editors
22195            .into_iter()
22196            .find(|new_editor| *new_editor != test_editor_cx.entity())
22197            .expect("Should have one non-test editor now")
22198            .read(test_editor_cx)
22199            .text(test_editor_cx);
22200        assert_eq!(
22201            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22202            "Should use the range from the references response and not the GoToDefinition one"
22203        );
22204    });
22205}
22206
22207#[gpui::test]
22208async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22209    init_test(cx, |_| {});
22210    cx.update(|cx| {
22211        let mut editor_settings = EditorSettings::get_global(cx).clone();
22212        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22213        EditorSettings::override_global(editor_settings, cx);
22214    });
22215    let mut cx = EditorLspTestContext::new_rust(
22216        lsp::ServerCapabilities {
22217            definition_provider: Some(lsp::OneOf::Left(true)),
22218            references_provider: Some(lsp::OneOf::Left(true)),
22219            ..lsp::ServerCapabilities::default()
22220        },
22221        cx,
22222    )
22223    .await;
22224    let original_state = r#"fn one() {
22225        let mut a = ˇtwo();
22226    }
22227
22228    fn two() {}"#
22229        .unindent();
22230    cx.set_state(&original_state);
22231
22232    let mut go_to_definition = cx
22233        .lsp
22234        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22235            move |_, _| async move { Ok(None) },
22236        );
22237    let _references = cx
22238        .lsp
22239        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22240            panic!("Should not call for references with no go to definition fallback")
22241        });
22242
22243    let navigated = cx
22244        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22245        .await
22246        .expect("Failed to navigate to lookup references");
22247    go_to_definition
22248        .next()
22249        .await
22250        .expect("Should have called the go_to_definition handler");
22251
22252    assert_eq!(
22253        navigated,
22254        Navigated::No,
22255        "Should have navigated to references as a fallback after empty GoToDefinition response"
22256    );
22257    cx.assert_editor_state(&original_state);
22258    let editors = cx.update_workspace(|workspace, _, cx| {
22259        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22260    });
22261    cx.update_editor(|_, _, _| {
22262        assert_eq!(
22263            editors.len(),
22264            1,
22265            "After unsuccessful fallback, no other editor should have been opened"
22266        );
22267    });
22268}
22269
22270#[gpui::test]
22271async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22272    init_test(cx, |_| {});
22273    let mut cx = EditorLspTestContext::new_rust(
22274        lsp::ServerCapabilities {
22275            references_provider: Some(lsp::OneOf::Left(true)),
22276            ..lsp::ServerCapabilities::default()
22277        },
22278        cx,
22279    )
22280    .await;
22281
22282    cx.set_state(
22283        &r#"
22284        fn one() {
22285            let mut a = two();
22286        }
22287
22288        fn ˇtwo() {}"#
22289            .unindent(),
22290    );
22291    cx.lsp
22292        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22293            Ok(Some(vec![
22294                lsp::Location {
22295                    uri: params.text_document_position.text_document.uri.clone(),
22296                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22297                },
22298                lsp::Location {
22299                    uri: params.text_document_position.text_document.uri,
22300                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22301                },
22302            ]))
22303        });
22304    let navigated = cx
22305        .update_editor(|editor, window, cx| {
22306            editor.find_all_references(&FindAllReferences, window, cx)
22307        })
22308        .unwrap()
22309        .await
22310        .expect("Failed to navigate to references");
22311    assert_eq!(
22312        navigated,
22313        Navigated::Yes,
22314        "Should have navigated to references from the FindAllReferences response"
22315    );
22316    cx.assert_editor_state(
22317        &r#"fn one() {
22318            let mut a = two();
22319        }
22320
22321        fn ˇtwo() {}"#
22322            .unindent(),
22323    );
22324
22325    let editors = cx.update_workspace(|workspace, _, cx| {
22326        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22327    });
22328    cx.update_editor(|_, _, _| {
22329        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22330    });
22331
22332    cx.set_state(
22333        &r#"fn one() {
22334            let mut a = ˇtwo();
22335        }
22336
22337        fn two() {}"#
22338            .unindent(),
22339    );
22340    let navigated = cx
22341        .update_editor(|editor, window, cx| {
22342            editor.find_all_references(&FindAllReferences, window, cx)
22343        })
22344        .unwrap()
22345        .await
22346        .expect("Failed to navigate to references");
22347    assert_eq!(
22348        navigated,
22349        Navigated::Yes,
22350        "Should have navigated to references from the FindAllReferences response"
22351    );
22352    cx.assert_editor_state(
22353        &r#"fn one() {
22354            let mut a = ˇtwo();
22355        }
22356
22357        fn two() {}"#
22358            .unindent(),
22359    );
22360    let editors = cx.update_workspace(|workspace, _, cx| {
22361        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22362    });
22363    cx.update_editor(|_, _, _| {
22364        assert_eq!(
22365            editors.len(),
22366            2,
22367            "should have re-used the previous multibuffer"
22368        );
22369    });
22370
22371    cx.set_state(
22372        &r#"fn one() {
22373            let mut a = ˇtwo();
22374        }
22375        fn three() {}
22376        fn two() {}"#
22377            .unindent(),
22378    );
22379    cx.lsp
22380        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22381            Ok(Some(vec![
22382                lsp::Location {
22383                    uri: params.text_document_position.text_document.uri.clone(),
22384                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22385                },
22386                lsp::Location {
22387                    uri: params.text_document_position.text_document.uri,
22388                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22389                },
22390            ]))
22391        });
22392    let navigated = cx
22393        .update_editor(|editor, window, cx| {
22394            editor.find_all_references(&FindAllReferences, window, cx)
22395        })
22396        .unwrap()
22397        .await
22398        .expect("Failed to navigate to references");
22399    assert_eq!(
22400        navigated,
22401        Navigated::Yes,
22402        "Should have navigated to references from the FindAllReferences response"
22403    );
22404    cx.assert_editor_state(
22405        &r#"fn one() {
22406                let mut a = ˇtwo();
22407            }
22408            fn three() {}
22409            fn two() {}"#
22410            .unindent(),
22411    );
22412    let editors = cx.update_workspace(|workspace, _, cx| {
22413        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22414    });
22415    cx.update_editor(|_, _, _| {
22416        assert_eq!(
22417            editors.len(),
22418            3,
22419            "should have used a new multibuffer as offsets changed"
22420        );
22421    });
22422}
22423#[gpui::test]
22424async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22425    init_test(cx, |_| {});
22426
22427    let language = Arc::new(Language::new(
22428        LanguageConfig::default(),
22429        Some(tree_sitter_rust::LANGUAGE.into()),
22430    ));
22431
22432    let text = r#"
22433        #[cfg(test)]
22434        mod tests() {
22435            #[test]
22436            fn runnable_1() {
22437                let a = 1;
22438            }
22439
22440            #[test]
22441            fn runnable_2() {
22442                let a = 1;
22443                let b = 2;
22444            }
22445        }
22446    "#
22447    .unindent();
22448
22449    let fs = FakeFs::new(cx.executor());
22450    fs.insert_file("/file.rs", Default::default()).await;
22451
22452    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22453    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22454    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22455    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22456    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22457
22458    let editor = cx.new_window_entity(|window, cx| {
22459        Editor::new(
22460            EditorMode::full(),
22461            multi_buffer,
22462            Some(project.clone()),
22463            window,
22464            cx,
22465        )
22466    });
22467
22468    editor.update_in(cx, |editor, window, cx| {
22469        let snapshot = editor.buffer().read(cx).snapshot(cx);
22470        editor.tasks.insert(
22471            (buffer.read(cx).remote_id(), 3),
22472            RunnableTasks {
22473                templates: vec![],
22474                offset: snapshot.anchor_before(MultiBufferOffset(43)),
22475                column: 0,
22476                extra_variables: HashMap::default(),
22477                context_range: BufferOffset(43)..BufferOffset(85),
22478            },
22479        );
22480        editor.tasks.insert(
22481            (buffer.read(cx).remote_id(), 8),
22482            RunnableTasks {
22483                templates: vec![],
22484                offset: snapshot.anchor_before(MultiBufferOffset(86)),
22485                column: 0,
22486                extra_variables: HashMap::default(),
22487                context_range: BufferOffset(86)..BufferOffset(191),
22488            },
22489        );
22490
22491        // Test finding task when cursor is inside function body
22492        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22493            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22494        });
22495        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22496        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22497
22498        // Test finding task when cursor is on function name
22499        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22500            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22501        });
22502        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22503        assert_eq!(row, 8, "Should find task when cursor is on function name");
22504    });
22505}
22506
22507#[gpui::test]
22508async fn test_folding_buffers(cx: &mut TestAppContext) {
22509    init_test(cx, |_| {});
22510
22511    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22512    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22513    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22514
22515    let fs = FakeFs::new(cx.executor());
22516    fs.insert_tree(
22517        path!("/a"),
22518        json!({
22519            "first.rs": sample_text_1,
22520            "second.rs": sample_text_2,
22521            "third.rs": sample_text_3,
22522        }),
22523    )
22524    .await;
22525    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22526    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22527    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22528    let worktree = project.update(cx, |project, cx| {
22529        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22530        assert_eq!(worktrees.len(), 1);
22531        worktrees.pop().unwrap()
22532    });
22533    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22534
22535    let buffer_1 = project
22536        .update(cx, |project, cx| {
22537            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22538        })
22539        .await
22540        .unwrap();
22541    let buffer_2 = project
22542        .update(cx, |project, cx| {
22543            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22544        })
22545        .await
22546        .unwrap();
22547    let buffer_3 = project
22548        .update(cx, |project, cx| {
22549            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22550        })
22551        .await
22552        .unwrap();
22553
22554    let multi_buffer = cx.new(|cx| {
22555        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22556        multi_buffer.push_excerpts(
22557            buffer_1.clone(),
22558            [
22559                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22560                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22561                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22562            ],
22563            cx,
22564        );
22565        multi_buffer.push_excerpts(
22566            buffer_2.clone(),
22567            [
22568                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22569                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22570                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22571            ],
22572            cx,
22573        );
22574        multi_buffer.push_excerpts(
22575            buffer_3.clone(),
22576            [
22577                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22578                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22579                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22580            ],
22581            cx,
22582        );
22583        multi_buffer
22584    });
22585    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22586        Editor::new(
22587            EditorMode::full(),
22588            multi_buffer.clone(),
22589            Some(project.clone()),
22590            window,
22591            cx,
22592        )
22593    });
22594
22595    assert_eq!(
22596        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22597        "\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",
22598    );
22599
22600    multi_buffer_editor.update(cx, |editor, cx| {
22601        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22602    });
22603    assert_eq!(
22604        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22605        "\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",
22606        "After folding the first buffer, its text should not be displayed"
22607    );
22608
22609    multi_buffer_editor.update(cx, |editor, cx| {
22610        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22611    });
22612    assert_eq!(
22613        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22614        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22615        "After folding the second buffer, its text should not be displayed"
22616    );
22617
22618    multi_buffer_editor.update(cx, |editor, cx| {
22619        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22620    });
22621    assert_eq!(
22622        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22623        "\n\n\n\n\n",
22624        "After folding the third buffer, its text should not be displayed"
22625    );
22626
22627    // Emulate selection inside the fold logic, that should work
22628    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22629        editor
22630            .snapshot(window, cx)
22631            .next_line_boundary(Point::new(0, 4));
22632    });
22633
22634    multi_buffer_editor.update(cx, |editor, cx| {
22635        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22636    });
22637    assert_eq!(
22638        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22639        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22640        "After unfolding the second buffer, its text should be displayed"
22641    );
22642
22643    // Typing inside of buffer 1 causes that buffer to be unfolded.
22644    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22645        assert_eq!(
22646            multi_buffer
22647                .read(cx)
22648                .snapshot(cx)
22649                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22650                .collect::<String>(),
22651            "bbbb"
22652        );
22653        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22654            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22655        });
22656        editor.handle_input("B", window, cx);
22657    });
22658
22659    assert_eq!(
22660        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22661        "\n\naaaa\nBbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22662        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22663    );
22664
22665    multi_buffer_editor.update(cx, |editor, cx| {
22666        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22667    });
22668    assert_eq!(
22669        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22670        "\n\naaaa\nBbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22671        "After unfolding the all buffers, all original text should be displayed"
22672    );
22673}
22674
22675#[gpui::test]
22676async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22677    init_test(cx, |_| {});
22678
22679    let sample_text_1 = "1111\n2222\n3333".to_string();
22680    let sample_text_2 = "4444\n5555\n6666".to_string();
22681    let sample_text_3 = "7777\n8888\n9999".to_string();
22682
22683    let fs = FakeFs::new(cx.executor());
22684    fs.insert_tree(
22685        path!("/a"),
22686        json!({
22687            "first.rs": sample_text_1,
22688            "second.rs": sample_text_2,
22689            "third.rs": sample_text_3,
22690        }),
22691    )
22692    .await;
22693    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22694    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22695    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22696    let worktree = project.update(cx, |project, cx| {
22697        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22698        assert_eq!(worktrees.len(), 1);
22699        worktrees.pop().unwrap()
22700    });
22701    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22702
22703    let buffer_1 = project
22704        .update(cx, |project, cx| {
22705            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22706        })
22707        .await
22708        .unwrap();
22709    let buffer_2 = project
22710        .update(cx, |project, cx| {
22711            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22712        })
22713        .await
22714        .unwrap();
22715    let buffer_3 = project
22716        .update(cx, |project, cx| {
22717            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22718        })
22719        .await
22720        .unwrap();
22721
22722    let multi_buffer = cx.new(|cx| {
22723        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22724        multi_buffer.push_excerpts(
22725            buffer_1.clone(),
22726            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22727            cx,
22728        );
22729        multi_buffer.push_excerpts(
22730            buffer_2.clone(),
22731            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22732            cx,
22733        );
22734        multi_buffer.push_excerpts(
22735            buffer_3.clone(),
22736            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22737            cx,
22738        );
22739        multi_buffer
22740    });
22741
22742    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22743        Editor::new(
22744            EditorMode::full(),
22745            multi_buffer,
22746            Some(project.clone()),
22747            window,
22748            cx,
22749        )
22750    });
22751
22752    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22753    assert_eq!(
22754        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22755        full_text,
22756    );
22757
22758    multi_buffer_editor.update(cx, |editor, cx| {
22759        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22760    });
22761    assert_eq!(
22762        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22763        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22764        "After folding the first buffer, its text should not be displayed"
22765    );
22766
22767    multi_buffer_editor.update(cx, |editor, cx| {
22768        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22769    });
22770
22771    assert_eq!(
22772        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22773        "\n\n\n\n\n\n7777\n8888\n9999",
22774        "After folding the second buffer, its text should not be displayed"
22775    );
22776
22777    multi_buffer_editor.update(cx, |editor, cx| {
22778        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22779    });
22780    assert_eq!(
22781        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22782        "\n\n\n\n\n",
22783        "After folding the third buffer, its text should not be displayed"
22784    );
22785
22786    multi_buffer_editor.update(cx, |editor, cx| {
22787        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22788    });
22789    assert_eq!(
22790        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22791        "\n\n\n\n4444\n5555\n6666\n\n",
22792        "After unfolding the second buffer, its text should be displayed"
22793    );
22794
22795    multi_buffer_editor.update(cx, |editor, cx| {
22796        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22797    });
22798    assert_eq!(
22799        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22800        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22801        "After unfolding the first buffer, its text should be displayed"
22802    );
22803
22804    multi_buffer_editor.update(cx, |editor, cx| {
22805        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22806    });
22807    assert_eq!(
22808        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22809        full_text,
22810        "After unfolding all buffers, all original text should be displayed"
22811    );
22812}
22813
22814#[gpui::test]
22815async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22816    init_test(cx, |_| {});
22817
22818    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22819
22820    let fs = FakeFs::new(cx.executor());
22821    fs.insert_tree(
22822        path!("/a"),
22823        json!({
22824            "main.rs": sample_text,
22825        }),
22826    )
22827    .await;
22828    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22829    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22830    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22831    let worktree = project.update(cx, |project, cx| {
22832        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22833        assert_eq!(worktrees.len(), 1);
22834        worktrees.pop().unwrap()
22835    });
22836    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22837
22838    let buffer_1 = project
22839        .update(cx, |project, cx| {
22840            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22841        })
22842        .await
22843        .unwrap();
22844
22845    let multi_buffer = cx.new(|cx| {
22846        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22847        multi_buffer.push_excerpts(
22848            buffer_1.clone(),
22849            [ExcerptRange::new(
22850                Point::new(0, 0)
22851                    ..Point::new(
22852                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22853                        0,
22854                    ),
22855            )],
22856            cx,
22857        );
22858        multi_buffer
22859    });
22860    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22861        Editor::new(
22862            EditorMode::full(),
22863            multi_buffer,
22864            Some(project.clone()),
22865            window,
22866            cx,
22867        )
22868    });
22869
22870    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22871    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22872        enum TestHighlight {}
22873        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22874        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22875        editor.highlight_text::<TestHighlight>(
22876            vec![highlight_range.clone()],
22877            HighlightStyle::color(Hsla::green()),
22878            cx,
22879        );
22880        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22881            s.select_ranges(Some(highlight_range))
22882        });
22883    });
22884
22885    let full_text = format!("\n\n{sample_text}");
22886    assert_eq!(
22887        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22888        full_text,
22889    );
22890}
22891
22892#[gpui::test]
22893async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22894    init_test(cx, |_| {});
22895    cx.update(|cx| {
22896        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22897            "keymaps/default-linux.json",
22898            cx,
22899        )
22900        .unwrap();
22901        cx.bind_keys(default_key_bindings);
22902    });
22903
22904    let (editor, cx) = cx.add_window_view(|window, cx| {
22905        let multi_buffer = MultiBuffer::build_multi(
22906            [
22907                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22908                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22909                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22910                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22911            ],
22912            cx,
22913        );
22914        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22915
22916        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22917        // fold all but the second buffer, so that we test navigating between two
22918        // adjacent folded buffers, as well as folded buffers at the start and
22919        // end the multibuffer
22920        editor.fold_buffer(buffer_ids[0], cx);
22921        editor.fold_buffer(buffer_ids[2], cx);
22922        editor.fold_buffer(buffer_ids[3], cx);
22923
22924        editor
22925    });
22926    cx.simulate_resize(size(px(1000.), px(1000.)));
22927
22928    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22929    cx.assert_excerpts_with_selections(indoc! {"
22930        [EXCERPT]
22931        ˇ[FOLDED]
22932        [EXCERPT]
22933        a1
22934        b1
22935        [EXCERPT]
22936        [FOLDED]
22937        [EXCERPT]
22938        [FOLDED]
22939        "
22940    });
22941    cx.simulate_keystroke("down");
22942    cx.assert_excerpts_with_selections(indoc! {"
22943        [EXCERPT]
22944        [FOLDED]
22945        [EXCERPT]
22946        ˇa1
22947        b1
22948        [EXCERPT]
22949        [FOLDED]
22950        [EXCERPT]
22951        [FOLDED]
22952        "
22953    });
22954    cx.simulate_keystroke("down");
22955    cx.assert_excerpts_with_selections(indoc! {"
22956        [EXCERPT]
22957        [FOLDED]
22958        [EXCERPT]
22959        a1
22960        ˇb1
22961        [EXCERPT]
22962        [FOLDED]
22963        [EXCERPT]
22964        [FOLDED]
22965        "
22966    });
22967    cx.simulate_keystroke("down");
22968    cx.assert_excerpts_with_selections(indoc! {"
22969        [EXCERPT]
22970        [FOLDED]
22971        [EXCERPT]
22972        a1
22973        b1
22974        ˇ[EXCERPT]
22975        [FOLDED]
22976        [EXCERPT]
22977        [FOLDED]
22978        "
22979    });
22980    cx.simulate_keystroke("down");
22981    cx.assert_excerpts_with_selections(indoc! {"
22982        [EXCERPT]
22983        [FOLDED]
22984        [EXCERPT]
22985        a1
22986        b1
22987        [EXCERPT]
22988        ˇ[FOLDED]
22989        [EXCERPT]
22990        [FOLDED]
22991        "
22992    });
22993    for _ in 0..5 {
22994        cx.simulate_keystroke("down");
22995        cx.assert_excerpts_with_selections(indoc! {"
22996            [EXCERPT]
22997            [FOLDED]
22998            [EXCERPT]
22999            a1
23000            b1
23001            [EXCERPT]
23002            [FOLDED]
23003            [EXCERPT]
23004            ˇ[FOLDED]
23005            "
23006        });
23007    }
23008
23009    cx.simulate_keystroke("up");
23010    cx.assert_excerpts_with_selections(indoc! {"
23011        [EXCERPT]
23012        [FOLDED]
23013        [EXCERPT]
23014        a1
23015        b1
23016        [EXCERPT]
23017        ˇ[FOLDED]
23018        [EXCERPT]
23019        [FOLDED]
23020        "
23021    });
23022    cx.simulate_keystroke("up");
23023    cx.assert_excerpts_with_selections(indoc! {"
23024        [EXCERPT]
23025        [FOLDED]
23026        [EXCERPT]
23027        a1
23028        b1
23029        ˇ[EXCERPT]
23030        [FOLDED]
23031        [EXCERPT]
23032        [FOLDED]
23033        "
23034    });
23035    cx.simulate_keystroke("up");
23036    cx.assert_excerpts_with_selections(indoc! {"
23037        [EXCERPT]
23038        [FOLDED]
23039        [EXCERPT]
23040        a1
23041        ˇb1
23042        [EXCERPT]
23043        [FOLDED]
23044        [EXCERPT]
23045        [FOLDED]
23046        "
23047    });
23048    cx.simulate_keystroke("up");
23049    cx.assert_excerpts_with_selections(indoc! {"
23050        [EXCERPT]
23051        [FOLDED]
23052        [EXCERPT]
23053        ˇa1
23054        b1
23055        [EXCERPT]
23056        [FOLDED]
23057        [EXCERPT]
23058        [FOLDED]
23059        "
23060    });
23061    for _ in 0..5 {
23062        cx.simulate_keystroke("up");
23063        cx.assert_excerpts_with_selections(indoc! {"
23064            [EXCERPT]
23065            ˇ[FOLDED]
23066            [EXCERPT]
23067            a1
23068            b1
23069            [EXCERPT]
23070            [FOLDED]
23071            [EXCERPT]
23072            [FOLDED]
23073            "
23074        });
23075    }
23076}
23077
23078#[gpui::test]
23079async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23080    init_test(cx, |_| {});
23081
23082    // Simple insertion
23083    assert_highlighted_edits(
23084        "Hello, world!",
23085        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23086        true,
23087        cx,
23088        |highlighted_edits, cx| {
23089            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23090            assert_eq!(highlighted_edits.highlights.len(), 1);
23091            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23092            assert_eq!(
23093                highlighted_edits.highlights[0].1.background_color,
23094                Some(cx.theme().status().created_background)
23095            );
23096        },
23097    )
23098    .await;
23099
23100    // Replacement
23101    assert_highlighted_edits(
23102        "This is a test.",
23103        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23104        false,
23105        cx,
23106        |highlighted_edits, cx| {
23107            assert_eq!(highlighted_edits.text, "That is a test.");
23108            assert_eq!(highlighted_edits.highlights.len(), 1);
23109            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23110            assert_eq!(
23111                highlighted_edits.highlights[0].1.background_color,
23112                Some(cx.theme().status().created_background)
23113            );
23114        },
23115    )
23116    .await;
23117
23118    // Multiple edits
23119    assert_highlighted_edits(
23120        "Hello, world!",
23121        vec![
23122            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23123            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23124        ],
23125        false,
23126        cx,
23127        |highlighted_edits, cx| {
23128            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23129            assert_eq!(highlighted_edits.highlights.len(), 2);
23130            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23131            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23132            assert_eq!(
23133                highlighted_edits.highlights[0].1.background_color,
23134                Some(cx.theme().status().created_background)
23135            );
23136            assert_eq!(
23137                highlighted_edits.highlights[1].1.background_color,
23138                Some(cx.theme().status().created_background)
23139            );
23140        },
23141    )
23142    .await;
23143
23144    // Multiple lines with edits
23145    assert_highlighted_edits(
23146        "First line\nSecond line\nThird line\nFourth line",
23147        vec![
23148            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23149            (
23150                Point::new(2, 0)..Point::new(2, 10),
23151                "New third line".to_string(),
23152            ),
23153            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23154        ],
23155        false,
23156        cx,
23157        |highlighted_edits, cx| {
23158            assert_eq!(
23159                highlighted_edits.text,
23160                "Second modified\nNew third line\nFourth updated line"
23161            );
23162            assert_eq!(highlighted_edits.highlights.len(), 3);
23163            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23164            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23165            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23166            for highlight in &highlighted_edits.highlights {
23167                assert_eq!(
23168                    highlight.1.background_color,
23169                    Some(cx.theme().status().created_background)
23170                );
23171            }
23172        },
23173    )
23174    .await;
23175}
23176
23177#[gpui::test]
23178async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23179    init_test(cx, |_| {});
23180
23181    // Deletion
23182    assert_highlighted_edits(
23183        "Hello, world!",
23184        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23185        true,
23186        cx,
23187        |highlighted_edits, cx| {
23188            assert_eq!(highlighted_edits.text, "Hello, world!");
23189            assert_eq!(highlighted_edits.highlights.len(), 1);
23190            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23191            assert_eq!(
23192                highlighted_edits.highlights[0].1.background_color,
23193                Some(cx.theme().status().deleted_background)
23194            );
23195        },
23196    )
23197    .await;
23198
23199    // Insertion
23200    assert_highlighted_edits(
23201        "Hello, world!",
23202        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23203        true,
23204        cx,
23205        |highlighted_edits, cx| {
23206            assert_eq!(highlighted_edits.highlights.len(), 1);
23207            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23208            assert_eq!(
23209                highlighted_edits.highlights[0].1.background_color,
23210                Some(cx.theme().status().created_background)
23211            );
23212        },
23213    )
23214    .await;
23215}
23216
23217async fn assert_highlighted_edits(
23218    text: &str,
23219    edits: Vec<(Range<Point>, String)>,
23220    include_deletions: bool,
23221    cx: &mut TestAppContext,
23222    assertion_fn: impl Fn(HighlightedText, &App),
23223) {
23224    let window = cx.add_window(|window, cx| {
23225        let buffer = MultiBuffer::build_simple(text, cx);
23226        Editor::new(EditorMode::full(), buffer, None, window, cx)
23227    });
23228    let cx = &mut VisualTestContext::from_window(*window, cx);
23229
23230    let (buffer, snapshot) = window
23231        .update(cx, |editor, _window, cx| {
23232            (
23233                editor.buffer().clone(),
23234                editor.buffer().read(cx).snapshot(cx),
23235            )
23236        })
23237        .unwrap();
23238
23239    let edits = edits
23240        .into_iter()
23241        .map(|(range, edit)| {
23242            (
23243                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23244                edit,
23245            )
23246        })
23247        .collect::<Vec<_>>();
23248
23249    let text_anchor_edits = edits
23250        .clone()
23251        .into_iter()
23252        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23253        .collect::<Vec<_>>();
23254
23255    let edit_preview = window
23256        .update(cx, |_, _window, cx| {
23257            buffer
23258                .read(cx)
23259                .as_singleton()
23260                .unwrap()
23261                .read(cx)
23262                .preview_edits(text_anchor_edits.into(), cx)
23263        })
23264        .unwrap()
23265        .await;
23266
23267    cx.update(|_window, cx| {
23268        let highlighted_edits = edit_prediction_edit_text(
23269            snapshot.as_singleton().unwrap().2,
23270            &edits,
23271            &edit_preview,
23272            include_deletions,
23273            cx,
23274        );
23275        assertion_fn(highlighted_edits, cx)
23276    });
23277}
23278
23279#[track_caller]
23280fn assert_breakpoint(
23281    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23282    path: &Arc<Path>,
23283    expected: Vec<(u32, Breakpoint)>,
23284) {
23285    if expected.is_empty() {
23286        assert!(!breakpoints.contains_key(path), "{}", path.display());
23287    } else {
23288        let mut breakpoint = breakpoints
23289            .get(path)
23290            .unwrap()
23291            .iter()
23292            .map(|breakpoint| {
23293                (
23294                    breakpoint.row,
23295                    Breakpoint {
23296                        message: breakpoint.message.clone(),
23297                        state: breakpoint.state,
23298                        condition: breakpoint.condition.clone(),
23299                        hit_condition: breakpoint.hit_condition.clone(),
23300                    },
23301                )
23302            })
23303            .collect::<Vec<_>>();
23304
23305        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23306
23307        assert_eq!(expected, breakpoint);
23308    }
23309}
23310
23311fn add_log_breakpoint_at_cursor(
23312    editor: &mut Editor,
23313    log_message: &str,
23314    window: &mut Window,
23315    cx: &mut Context<Editor>,
23316) {
23317    let (anchor, bp) = editor
23318        .breakpoints_at_cursors(window, cx)
23319        .first()
23320        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23321        .unwrap_or_else(|| {
23322            let snapshot = editor.snapshot(window, cx);
23323            let cursor_position: Point =
23324                editor.selections.newest(&snapshot.display_snapshot).head();
23325
23326            let breakpoint_position = snapshot
23327                .buffer_snapshot()
23328                .anchor_before(Point::new(cursor_position.row, 0));
23329
23330            (breakpoint_position, Breakpoint::new_log(log_message))
23331        });
23332
23333    editor.edit_breakpoint_at_anchor(
23334        anchor,
23335        bp,
23336        BreakpointEditAction::EditLogMessage(log_message.into()),
23337        cx,
23338    );
23339}
23340
23341#[gpui::test]
23342async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23343    init_test(cx, |_| {});
23344
23345    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23346    let fs = FakeFs::new(cx.executor());
23347    fs.insert_tree(
23348        path!("/a"),
23349        json!({
23350            "main.rs": sample_text,
23351        }),
23352    )
23353    .await;
23354    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23355    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23356    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23357
23358    let fs = FakeFs::new(cx.executor());
23359    fs.insert_tree(
23360        path!("/a"),
23361        json!({
23362            "main.rs": sample_text,
23363        }),
23364    )
23365    .await;
23366    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23367    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23368    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23369    let worktree_id = workspace
23370        .update(cx, |workspace, _window, cx| {
23371            workspace.project().update(cx, |project, cx| {
23372                project.worktrees(cx).next().unwrap().read(cx).id()
23373            })
23374        })
23375        .unwrap();
23376
23377    let buffer = project
23378        .update(cx, |project, cx| {
23379            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23380        })
23381        .await
23382        .unwrap();
23383
23384    let (editor, cx) = cx.add_window_view(|window, cx| {
23385        Editor::new(
23386            EditorMode::full(),
23387            MultiBuffer::build_from_buffer(buffer, cx),
23388            Some(project.clone()),
23389            window,
23390            cx,
23391        )
23392    });
23393
23394    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23395    let abs_path = project.read_with(cx, |project, cx| {
23396        project
23397            .absolute_path(&project_path, cx)
23398            .map(Arc::from)
23399            .unwrap()
23400    });
23401
23402    // assert we can add breakpoint on the first line
23403    editor.update_in(cx, |editor, window, cx| {
23404        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23405        editor.move_to_end(&MoveToEnd, window, cx);
23406        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23407    });
23408
23409    let breakpoints = editor.update(cx, |editor, cx| {
23410        editor
23411            .breakpoint_store()
23412            .as_ref()
23413            .unwrap()
23414            .read(cx)
23415            .all_source_breakpoints(cx)
23416    });
23417
23418    assert_eq!(1, breakpoints.len());
23419    assert_breakpoint(
23420        &breakpoints,
23421        &abs_path,
23422        vec![
23423            (0, Breakpoint::new_standard()),
23424            (3, Breakpoint::new_standard()),
23425        ],
23426    );
23427
23428    editor.update_in(cx, |editor, window, cx| {
23429        editor.move_to_beginning(&MoveToBeginning, window, cx);
23430        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23431    });
23432
23433    let breakpoints = editor.update(cx, |editor, cx| {
23434        editor
23435            .breakpoint_store()
23436            .as_ref()
23437            .unwrap()
23438            .read(cx)
23439            .all_source_breakpoints(cx)
23440    });
23441
23442    assert_eq!(1, breakpoints.len());
23443    assert_breakpoint(
23444        &breakpoints,
23445        &abs_path,
23446        vec![(3, Breakpoint::new_standard())],
23447    );
23448
23449    editor.update_in(cx, |editor, window, cx| {
23450        editor.move_to_end(&MoveToEnd, window, cx);
23451        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23452    });
23453
23454    let breakpoints = editor.update(cx, |editor, cx| {
23455        editor
23456            .breakpoint_store()
23457            .as_ref()
23458            .unwrap()
23459            .read(cx)
23460            .all_source_breakpoints(cx)
23461    });
23462
23463    assert_eq!(0, breakpoints.len());
23464    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23465}
23466
23467#[gpui::test]
23468async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23469    init_test(cx, |_| {});
23470
23471    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23472
23473    let fs = FakeFs::new(cx.executor());
23474    fs.insert_tree(
23475        path!("/a"),
23476        json!({
23477            "main.rs": sample_text,
23478        }),
23479    )
23480    .await;
23481    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23482    let (workspace, cx) =
23483        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23484
23485    let worktree_id = workspace.update(cx, |workspace, cx| {
23486        workspace.project().update(cx, |project, cx| {
23487            project.worktrees(cx).next().unwrap().read(cx).id()
23488        })
23489    });
23490
23491    let buffer = project
23492        .update(cx, |project, cx| {
23493            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23494        })
23495        .await
23496        .unwrap();
23497
23498    let (editor, cx) = cx.add_window_view(|window, cx| {
23499        Editor::new(
23500            EditorMode::full(),
23501            MultiBuffer::build_from_buffer(buffer, cx),
23502            Some(project.clone()),
23503            window,
23504            cx,
23505        )
23506    });
23507
23508    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23509    let abs_path = project.read_with(cx, |project, cx| {
23510        project
23511            .absolute_path(&project_path, cx)
23512            .map(Arc::from)
23513            .unwrap()
23514    });
23515
23516    editor.update_in(cx, |editor, window, cx| {
23517        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23518    });
23519
23520    let breakpoints = editor.update(cx, |editor, cx| {
23521        editor
23522            .breakpoint_store()
23523            .as_ref()
23524            .unwrap()
23525            .read(cx)
23526            .all_source_breakpoints(cx)
23527    });
23528
23529    assert_breakpoint(
23530        &breakpoints,
23531        &abs_path,
23532        vec![(0, Breakpoint::new_log("hello world"))],
23533    );
23534
23535    // Removing a log message from a log breakpoint should remove it
23536    editor.update_in(cx, |editor, window, cx| {
23537        add_log_breakpoint_at_cursor(editor, "", window, cx);
23538    });
23539
23540    let breakpoints = editor.update(cx, |editor, cx| {
23541        editor
23542            .breakpoint_store()
23543            .as_ref()
23544            .unwrap()
23545            .read(cx)
23546            .all_source_breakpoints(cx)
23547    });
23548
23549    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23550
23551    editor.update_in(cx, |editor, window, cx| {
23552        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23553        editor.move_to_end(&MoveToEnd, window, cx);
23554        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23555        // Not adding a log message to a standard breakpoint shouldn't remove it
23556        add_log_breakpoint_at_cursor(editor, "", window, cx);
23557    });
23558
23559    let breakpoints = editor.update(cx, |editor, cx| {
23560        editor
23561            .breakpoint_store()
23562            .as_ref()
23563            .unwrap()
23564            .read(cx)
23565            .all_source_breakpoints(cx)
23566    });
23567
23568    assert_breakpoint(
23569        &breakpoints,
23570        &abs_path,
23571        vec![
23572            (0, Breakpoint::new_standard()),
23573            (3, Breakpoint::new_standard()),
23574        ],
23575    );
23576
23577    editor.update_in(cx, |editor, window, cx| {
23578        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23579    });
23580
23581    let breakpoints = editor.update(cx, |editor, cx| {
23582        editor
23583            .breakpoint_store()
23584            .as_ref()
23585            .unwrap()
23586            .read(cx)
23587            .all_source_breakpoints(cx)
23588    });
23589
23590    assert_breakpoint(
23591        &breakpoints,
23592        &abs_path,
23593        vec![
23594            (0, Breakpoint::new_standard()),
23595            (3, Breakpoint::new_log("hello world")),
23596        ],
23597    );
23598
23599    editor.update_in(cx, |editor, window, cx| {
23600        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23601    });
23602
23603    let breakpoints = editor.update(cx, |editor, cx| {
23604        editor
23605            .breakpoint_store()
23606            .as_ref()
23607            .unwrap()
23608            .read(cx)
23609            .all_source_breakpoints(cx)
23610    });
23611
23612    assert_breakpoint(
23613        &breakpoints,
23614        &abs_path,
23615        vec![
23616            (0, Breakpoint::new_standard()),
23617            (3, Breakpoint::new_log("hello Earth!!")),
23618        ],
23619    );
23620}
23621
23622/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23623/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23624/// or when breakpoints were placed out of order. This tests for a regression too
23625#[gpui::test]
23626async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23627    init_test(cx, |_| {});
23628
23629    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23630    let fs = FakeFs::new(cx.executor());
23631    fs.insert_tree(
23632        path!("/a"),
23633        json!({
23634            "main.rs": sample_text,
23635        }),
23636    )
23637    .await;
23638    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23639    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23640    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23641
23642    let fs = FakeFs::new(cx.executor());
23643    fs.insert_tree(
23644        path!("/a"),
23645        json!({
23646            "main.rs": sample_text,
23647        }),
23648    )
23649    .await;
23650    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23651    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23652    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23653    let worktree_id = workspace
23654        .update(cx, |workspace, _window, cx| {
23655            workspace.project().update(cx, |project, cx| {
23656                project.worktrees(cx).next().unwrap().read(cx).id()
23657            })
23658        })
23659        .unwrap();
23660
23661    let buffer = project
23662        .update(cx, |project, cx| {
23663            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23664        })
23665        .await
23666        .unwrap();
23667
23668    let (editor, cx) = cx.add_window_view(|window, cx| {
23669        Editor::new(
23670            EditorMode::full(),
23671            MultiBuffer::build_from_buffer(buffer, cx),
23672            Some(project.clone()),
23673            window,
23674            cx,
23675        )
23676    });
23677
23678    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23679    let abs_path = project.read_with(cx, |project, cx| {
23680        project
23681            .absolute_path(&project_path, cx)
23682            .map(Arc::from)
23683            .unwrap()
23684    });
23685
23686    // assert we can add breakpoint on the first line
23687    editor.update_in(cx, |editor, window, cx| {
23688        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23689        editor.move_to_end(&MoveToEnd, window, cx);
23690        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23691        editor.move_up(&MoveUp, window, cx);
23692        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23693    });
23694
23695    let breakpoints = editor.update(cx, |editor, cx| {
23696        editor
23697            .breakpoint_store()
23698            .as_ref()
23699            .unwrap()
23700            .read(cx)
23701            .all_source_breakpoints(cx)
23702    });
23703
23704    assert_eq!(1, breakpoints.len());
23705    assert_breakpoint(
23706        &breakpoints,
23707        &abs_path,
23708        vec![
23709            (0, Breakpoint::new_standard()),
23710            (2, Breakpoint::new_standard()),
23711            (3, Breakpoint::new_standard()),
23712        ],
23713    );
23714
23715    editor.update_in(cx, |editor, window, cx| {
23716        editor.move_to_beginning(&MoveToBeginning, window, cx);
23717        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23718        editor.move_to_end(&MoveToEnd, window, cx);
23719        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23720        // Disabling a breakpoint that doesn't exist should do nothing
23721        editor.move_up(&MoveUp, window, cx);
23722        editor.move_up(&MoveUp, window, cx);
23723        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23724    });
23725
23726    let breakpoints = editor.update(cx, |editor, cx| {
23727        editor
23728            .breakpoint_store()
23729            .as_ref()
23730            .unwrap()
23731            .read(cx)
23732            .all_source_breakpoints(cx)
23733    });
23734
23735    let disable_breakpoint = {
23736        let mut bp = Breakpoint::new_standard();
23737        bp.state = BreakpointState::Disabled;
23738        bp
23739    };
23740
23741    assert_eq!(1, breakpoints.len());
23742    assert_breakpoint(
23743        &breakpoints,
23744        &abs_path,
23745        vec![
23746            (0, disable_breakpoint.clone()),
23747            (2, Breakpoint::new_standard()),
23748            (3, disable_breakpoint.clone()),
23749        ],
23750    );
23751
23752    editor.update_in(cx, |editor, window, cx| {
23753        editor.move_to_beginning(&MoveToBeginning, window, cx);
23754        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23755        editor.move_to_end(&MoveToEnd, window, cx);
23756        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23757        editor.move_up(&MoveUp, window, cx);
23758        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23759    });
23760
23761    let breakpoints = editor.update(cx, |editor, cx| {
23762        editor
23763            .breakpoint_store()
23764            .as_ref()
23765            .unwrap()
23766            .read(cx)
23767            .all_source_breakpoints(cx)
23768    });
23769
23770    assert_eq!(1, breakpoints.len());
23771    assert_breakpoint(
23772        &breakpoints,
23773        &abs_path,
23774        vec![
23775            (0, Breakpoint::new_standard()),
23776            (2, disable_breakpoint),
23777            (3, Breakpoint::new_standard()),
23778        ],
23779    );
23780}
23781
23782#[gpui::test]
23783async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23784    init_test(cx, |_| {});
23785    let capabilities = lsp::ServerCapabilities {
23786        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23787            prepare_provider: Some(true),
23788            work_done_progress_options: Default::default(),
23789        })),
23790        ..Default::default()
23791    };
23792    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23793
23794    cx.set_state(indoc! {"
23795        struct Fˇoo {}
23796    "});
23797
23798    cx.update_editor(|editor, _, cx| {
23799        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23800        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23801        editor.highlight_background::<DocumentHighlightRead>(
23802            &[highlight_range],
23803            |theme| theme.colors().editor_document_highlight_read_background,
23804            cx,
23805        );
23806    });
23807
23808    let mut prepare_rename_handler = cx
23809        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23810            move |_, _, _| async move {
23811                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23812                    start: lsp::Position {
23813                        line: 0,
23814                        character: 7,
23815                    },
23816                    end: lsp::Position {
23817                        line: 0,
23818                        character: 10,
23819                    },
23820                })))
23821            },
23822        );
23823    let prepare_rename_task = cx
23824        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23825        .expect("Prepare rename was not started");
23826    prepare_rename_handler.next().await.unwrap();
23827    prepare_rename_task.await.expect("Prepare rename failed");
23828
23829    let mut rename_handler =
23830        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23831            let edit = lsp::TextEdit {
23832                range: lsp::Range {
23833                    start: lsp::Position {
23834                        line: 0,
23835                        character: 7,
23836                    },
23837                    end: lsp::Position {
23838                        line: 0,
23839                        character: 10,
23840                    },
23841                },
23842                new_text: "FooRenamed".to_string(),
23843            };
23844            Ok(Some(lsp::WorkspaceEdit::new(
23845                // Specify the same edit twice
23846                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23847            )))
23848        });
23849    let rename_task = cx
23850        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23851        .expect("Confirm rename was not started");
23852    rename_handler.next().await.unwrap();
23853    rename_task.await.expect("Confirm rename failed");
23854    cx.run_until_parked();
23855
23856    // Despite two edits, only one is actually applied as those are identical
23857    cx.assert_editor_state(indoc! {"
23858        struct FooRenamedˇ {}
23859    "});
23860}
23861
23862#[gpui::test]
23863async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23864    init_test(cx, |_| {});
23865    // These capabilities indicate that the server does not support prepare rename.
23866    let capabilities = lsp::ServerCapabilities {
23867        rename_provider: Some(lsp::OneOf::Left(true)),
23868        ..Default::default()
23869    };
23870    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23871
23872    cx.set_state(indoc! {"
23873        struct Fˇoo {}
23874    "});
23875
23876    cx.update_editor(|editor, _window, cx| {
23877        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23878        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23879        editor.highlight_background::<DocumentHighlightRead>(
23880            &[highlight_range],
23881            |theme| theme.colors().editor_document_highlight_read_background,
23882            cx,
23883        );
23884    });
23885
23886    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23887        .expect("Prepare rename was not started")
23888        .await
23889        .expect("Prepare rename failed");
23890
23891    let mut rename_handler =
23892        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23893            let edit = lsp::TextEdit {
23894                range: lsp::Range {
23895                    start: lsp::Position {
23896                        line: 0,
23897                        character: 7,
23898                    },
23899                    end: lsp::Position {
23900                        line: 0,
23901                        character: 10,
23902                    },
23903                },
23904                new_text: "FooRenamed".to_string(),
23905            };
23906            Ok(Some(lsp::WorkspaceEdit::new(
23907                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23908            )))
23909        });
23910    let rename_task = cx
23911        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23912        .expect("Confirm rename was not started");
23913    rename_handler.next().await.unwrap();
23914    rename_task.await.expect("Confirm rename failed");
23915    cx.run_until_parked();
23916
23917    // Correct range is renamed, as `surrounding_word` is used to find it.
23918    cx.assert_editor_state(indoc! {"
23919        struct FooRenamedˇ {}
23920    "});
23921}
23922
23923#[gpui::test]
23924async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23925    init_test(cx, |_| {});
23926    let mut cx = EditorTestContext::new(cx).await;
23927
23928    let language = Arc::new(
23929        Language::new(
23930            LanguageConfig::default(),
23931            Some(tree_sitter_html::LANGUAGE.into()),
23932        )
23933        .with_brackets_query(
23934            r#"
23935            ("<" @open "/>" @close)
23936            ("</" @open ">" @close)
23937            ("<" @open ">" @close)
23938            ("\"" @open "\"" @close)
23939            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23940        "#,
23941        )
23942        .unwrap(),
23943    );
23944    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23945
23946    cx.set_state(indoc! {"
23947        <span>ˇ</span>
23948    "});
23949    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23950    cx.assert_editor_state(indoc! {"
23951        <span>
23952        ˇ
23953        </span>
23954    "});
23955
23956    cx.set_state(indoc! {"
23957        <span><span></span>ˇ</span>
23958    "});
23959    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23960    cx.assert_editor_state(indoc! {"
23961        <span><span></span>
23962        ˇ</span>
23963    "});
23964
23965    cx.set_state(indoc! {"
23966        <span>ˇ
23967        </span>
23968    "});
23969    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23970    cx.assert_editor_state(indoc! {"
23971        <span>
23972        ˇ
23973        </span>
23974    "});
23975}
23976
23977#[gpui::test(iterations = 10)]
23978async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23979    init_test(cx, |_| {});
23980
23981    let fs = FakeFs::new(cx.executor());
23982    fs.insert_tree(
23983        path!("/dir"),
23984        json!({
23985            "a.ts": "a",
23986        }),
23987    )
23988    .await;
23989
23990    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23991    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23992    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23993
23994    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23995    language_registry.add(Arc::new(Language::new(
23996        LanguageConfig {
23997            name: "TypeScript".into(),
23998            matcher: LanguageMatcher {
23999                path_suffixes: vec!["ts".to_string()],
24000                ..Default::default()
24001            },
24002            ..Default::default()
24003        },
24004        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24005    )));
24006    let mut fake_language_servers = language_registry.register_fake_lsp(
24007        "TypeScript",
24008        FakeLspAdapter {
24009            capabilities: lsp::ServerCapabilities {
24010                code_lens_provider: Some(lsp::CodeLensOptions {
24011                    resolve_provider: Some(true),
24012                }),
24013                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24014                    commands: vec!["_the/command".to_string()],
24015                    ..lsp::ExecuteCommandOptions::default()
24016                }),
24017                ..lsp::ServerCapabilities::default()
24018            },
24019            ..FakeLspAdapter::default()
24020        },
24021    );
24022
24023    let editor = workspace
24024        .update(cx, |workspace, window, cx| {
24025            workspace.open_abs_path(
24026                PathBuf::from(path!("/dir/a.ts")),
24027                OpenOptions::default(),
24028                window,
24029                cx,
24030            )
24031        })
24032        .unwrap()
24033        .await
24034        .unwrap()
24035        .downcast::<Editor>()
24036        .unwrap();
24037    cx.executor().run_until_parked();
24038
24039    let fake_server = fake_language_servers.next().await.unwrap();
24040
24041    let buffer = editor.update(cx, |editor, cx| {
24042        editor
24043            .buffer()
24044            .read(cx)
24045            .as_singleton()
24046            .expect("have opened a single file by path")
24047    });
24048
24049    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24050    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24051    drop(buffer_snapshot);
24052    let actions = cx
24053        .update_window(*workspace, |_, window, cx| {
24054            project.code_actions(&buffer, anchor..anchor, window, cx)
24055        })
24056        .unwrap();
24057
24058    fake_server
24059        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24060            Ok(Some(vec![
24061                lsp::CodeLens {
24062                    range: lsp::Range::default(),
24063                    command: Some(lsp::Command {
24064                        title: "Code lens command".to_owned(),
24065                        command: "_the/command".to_owned(),
24066                        arguments: None,
24067                    }),
24068                    data: None,
24069                },
24070                lsp::CodeLens {
24071                    range: lsp::Range::default(),
24072                    command: Some(lsp::Command {
24073                        title: "Command not in capabilities".to_owned(),
24074                        command: "not in capabilities".to_owned(),
24075                        arguments: None,
24076                    }),
24077                    data: None,
24078                },
24079                lsp::CodeLens {
24080                    range: lsp::Range {
24081                        start: lsp::Position {
24082                            line: 1,
24083                            character: 1,
24084                        },
24085                        end: lsp::Position {
24086                            line: 1,
24087                            character: 1,
24088                        },
24089                    },
24090                    command: Some(lsp::Command {
24091                        title: "Command not in range".to_owned(),
24092                        command: "_the/command".to_owned(),
24093                        arguments: None,
24094                    }),
24095                    data: None,
24096                },
24097            ]))
24098        })
24099        .next()
24100        .await;
24101
24102    let actions = actions.await.unwrap();
24103    assert_eq!(
24104        actions.len(),
24105        1,
24106        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24107    );
24108    let action = actions[0].clone();
24109    let apply = project.update(cx, |project, cx| {
24110        project.apply_code_action(buffer.clone(), action, true, cx)
24111    });
24112
24113    // Resolving the code action does not populate its edits. In absence of
24114    // edits, we must execute the given command.
24115    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24116        |mut lens, _| async move {
24117            let lens_command = lens.command.as_mut().expect("should have a command");
24118            assert_eq!(lens_command.title, "Code lens command");
24119            lens_command.arguments = Some(vec![json!("the-argument")]);
24120            Ok(lens)
24121        },
24122    );
24123
24124    // While executing the command, the language server sends the editor
24125    // a `workspaceEdit` request.
24126    fake_server
24127        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24128            let fake = fake_server.clone();
24129            move |params, _| {
24130                assert_eq!(params.command, "_the/command");
24131                let fake = fake.clone();
24132                async move {
24133                    fake.server
24134                        .request::<lsp::request::ApplyWorkspaceEdit>(
24135                            lsp::ApplyWorkspaceEditParams {
24136                                label: None,
24137                                edit: lsp::WorkspaceEdit {
24138                                    changes: Some(
24139                                        [(
24140                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24141                                            vec![lsp::TextEdit {
24142                                                range: lsp::Range::new(
24143                                                    lsp::Position::new(0, 0),
24144                                                    lsp::Position::new(0, 0),
24145                                                ),
24146                                                new_text: "X".into(),
24147                                            }],
24148                                        )]
24149                                        .into_iter()
24150                                        .collect(),
24151                                    ),
24152                                    ..lsp::WorkspaceEdit::default()
24153                                },
24154                            },
24155                        )
24156                        .await
24157                        .into_response()
24158                        .unwrap();
24159                    Ok(Some(json!(null)))
24160                }
24161            }
24162        })
24163        .next()
24164        .await;
24165
24166    // Applying the code lens command returns a project transaction containing the edits
24167    // sent by the language server in its `workspaceEdit` request.
24168    let transaction = apply.await.unwrap();
24169    assert!(transaction.0.contains_key(&buffer));
24170    buffer.update(cx, |buffer, cx| {
24171        assert_eq!(buffer.text(), "Xa");
24172        buffer.undo(cx);
24173        assert_eq!(buffer.text(), "a");
24174    });
24175
24176    let actions_after_edits = cx
24177        .update_window(*workspace, |_, window, cx| {
24178            project.code_actions(&buffer, anchor..anchor, window, cx)
24179        })
24180        .unwrap()
24181        .await
24182        .unwrap();
24183    assert_eq!(
24184        actions, actions_after_edits,
24185        "For the same selection, same code lens actions should be returned"
24186    );
24187
24188    let _responses =
24189        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24190            panic!("No more code lens requests are expected");
24191        });
24192    editor.update_in(cx, |editor, window, cx| {
24193        editor.select_all(&SelectAll, window, cx);
24194    });
24195    cx.executor().run_until_parked();
24196    let new_actions = cx
24197        .update_window(*workspace, |_, window, cx| {
24198            project.code_actions(&buffer, anchor..anchor, window, cx)
24199        })
24200        .unwrap()
24201        .await
24202        .unwrap();
24203    assert_eq!(
24204        actions, new_actions,
24205        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24206    );
24207}
24208
24209#[gpui::test]
24210async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24211    init_test(cx, |_| {});
24212
24213    let fs = FakeFs::new(cx.executor());
24214    let main_text = r#"fn main() {
24215println!("1");
24216println!("2");
24217println!("3");
24218println!("4");
24219println!("5");
24220}"#;
24221    let lib_text = "mod foo {}";
24222    fs.insert_tree(
24223        path!("/a"),
24224        json!({
24225            "lib.rs": lib_text,
24226            "main.rs": main_text,
24227        }),
24228    )
24229    .await;
24230
24231    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24232    let (workspace, cx) =
24233        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24234    let worktree_id = workspace.update(cx, |workspace, cx| {
24235        workspace.project().update(cx, |project, cx| {
24236            project.worktrees(cx).next().unwrap().read(cx).id()
24237        })
24238    });
24239
24240    let expected_ranges = vec![
24241        Point::new(0, 0)..Point::new(0, 0),
24242        Point::new(1, 0)..Point::new(1, 1),
24243        Point::new(2, 0)..Point::new(2, 2),
24244        Point::new(3, 0)..Point::new(3, 3),
24245    ];
24246
24247    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24248    let editor_1 = workspace
24249        .update_in(cx, |workspace, window, cx| {
24250            workspace.open_path(
24251                (worktree_id, rel_path("main.rs")),
24252                Some(pane_1.downgrade()),
24253                true,
24254                window,
24255                cx,
24256            )
24257        })
24258        .unwrap()
24259        .await
24260        .downcast::<Editor>()
24261        .unwrap();
24262    pane_1.update(cx, |pane, cx| {
24263        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24264        open_editor.update(cx, |editor, cx| {
24265            assert_eq!(
24266                editor.display_text(cx),
24267                main_text,
24268                "Original main.rs text on initial open",
24269            );
24270            assert_eq!(
24271                editor
24272                    .selections
24273                    .all::<Point>(&editor.display_snapshot(cx))
24274                    .into_iter()
24275                    .map(|s| s.range())
24276                    .collect::<Vec<_>>(),
24277                vec![Point::zero()..Point::zero()],
24278                "Default selections on initial open",
24279            );
24280        })
24281    });
24282    editor_1.update_in(cx, |editor, window, cx| {
24283        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24284            s.select_ranges(expected_ranges.clone());
24285        });
24286    });
24287
24288    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24289        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24290    });
24291    let editor_2 = workspace
24292        .update_in(cx, |workspace, window, cx| {
24293            workspace.open_path(
24294                (worktree_id, rel_path("main.rs")),
24295                Some(pane_2.downgrade()),
24296                true,
24297                window,
24298                cx,
24299            )
24300        })
24301        .unwrap()
24302        .await
24303        .downcast::<Editor>()
24304        .unwrap();
24305    pane_2.update(cx, |pane, cx| {
24306        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24307        open_editor.update(cx, |editor, cx| {
24308            assert_eq!(
24309                editor.display_text(cx),
24310                main_text,
24311                "Original main.rs text on initial open in another panel",
24312            );
24313            assert_eq!(
24314                editor
24315                    .selections
24316                    .all::<Point>(&editor.display_snapshot(cx))
24317                    .into_iter()
24318                    .map(|s| s.range())
24319                    .collect::<Vec<_>>(),
24320                vec![Point::zero()..Point::zero()],
24321                "Default selections on initial open in another panel",
24322            );
24323        })
24324    });
24325
24326    editor_2.update_in(cx, |editor, window, cx| {
24327        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24328    });
24329
24330    let _other_editor_1 = workspace
24331        .update_in(cx, |workspace, window, cx| {
24332            workspace.open_path(
24333                (worktree_id, rel_path("lib.rs")),
24334                Some(pane_1.downgrade()),
24335                true,
24336                window,
24337                cx,
24338            )
24339        })
24340        .unwrap()
24341        .await
24342        .downcast::<Editor>()
24343        .unwrap();
24344    pane_1
24345        .update_in(cx, |pane, window, cx| {
24346            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24347        })
24348        .await
24349        .unwrap();
24350    drop(editor_1);
24351    pane_1.update(cx, |pane, cx| {
24352        pane.active_item()
24353            .unwrap()
24354            .downcast::<Editor>()
24355            .unwrap()
24356            .update(cx, |editor, cx| {
24357                assert_eq!(
24358                    editor.display_text(cx),
24359                    lib_text,
24360                    "Other file should be open and active",
24361                );
24362            });
24363        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24364    });
24365
24366    let _other_editor_2 = workspace
24367        .update_in(cx, |workspace, window, cx| {
24368            workspace.open_path(
24369                (worktree_id, rel_path("lib.rs")),
24370                Some(pane_2.downgrade()),
24371                true,
24372                window,
24373                cx,
24374            )
24375        })
24376        .unwrap()
24377        .await
24378        .downcast::<Editor>()
24379        .unwrap();
24380    pane_2
24381        .update_in(cx, |pane, window, cx| {
24382            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24383        })
24384        .await
24385        .unwrap();
24386    drop(editor_2);
24387    pane_2.update(cx, |pane, cx| {
24388        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24389        open_editor.update(cx, |editor, cx| {
24390            assert_eq!(
24391                editor.display_text(cx),
24392                lib_text,
24393                "Other file should be open and active in another panel too",
24394            );
24395        });
24396        assert_eq!(
24397            pane.items().count(),
24398            1,
24399            "No other editors should be open in another pane",
24400        );
24401    });
24402
24403    let _editor_1_reopened = workspace
24404        .update_in(cx, |workspace, window, cx| {
24405            workspace.open_path(
24406                (worktree_id, rel_path("main.rs")),
24407                Some(pane_1.downgrade()),
24408                true,
24409                window,
24410                cx,
24411            )
24412        })
24413        .unwrap()
24414        .await
24415        .downcast::<Editor>()
24416        .unwrap();
24417    let _editor_2_reopened = workspace
24418        .update_in(cx, |workspace, window, cx| {
24419            workspace.open_path(
24420                (worktree_id, rel_path("main.rs")),
24421                Some(pane_2.downgrade()),
24422                true,
24423                window,
24424                cx,
24425            )
24426        })
24427        .unwrap()
24428        .await
24429        .downcast::<Editor>()
24430        .unwrap();
24431    pane_1.update(cx, |pane, cx| {
24432        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24433        open_editor.update(cx, |editor, cx| {
24434            assert_eq!(
24435                editor.display_text(cx),
24436                main_text,
24437                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24438            );
24439            assert_eq!(
24440                editor
24441                    .selections
24442                    .all::<Point>(&editor.display_snapshot(cx))
24443                    .into_iter()
24444                    .map(|s| s.range())
24445                    .collect::<Vec<_>>(),
24446                expected_ranges,
24447                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24448            );
24449        })
24450    });
24451    pane_2.update(cx, |pane, cx| {
24452        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24453        open_editor.update(cx, |editor, cx| {
24454            assert_eq!(
24455                editor.display_text(cx),
24456                r#"fn main() {
24457⋯rintln!("1");
24458⋯intln!("2");
24459⋯ntln!("3");
24460println!("4");
24461println!("5");
24462}"#,
24463                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24464            );
24465            assert_eq!(
24466                editor
24467                    .selections
24468                    .all::<Point>(&editor.display_snapshot(cx))
24469                    .into_iter()
24470                    .map(|s| s.range())
24471                    .collect::<Vec<_>>(),
24472                vec![Point::zero()..Point::zero()],
24473                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24474            );
24475        })
24476    });
24477}
24478
24479#[gpui::test]
24480async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24481    init_test(cx, |_| {});
24482
24483    let fs = FakeFs::new(cx.executor());
24484    let main_text = r#"fn main() {
24485println!("1");
24486println!("2");
24487println!("3");
24488println!("4");
24489println!("5");
24490}"#;
24491    let lib_text = "mod foo {}";
24492    fs.insert_tree(
24493        path!("/a"),
24494        json!({
24495            "lib.rs": lib_text,
24496            "main.rs": main_text,
24497        }),
24498    )
24499    .await;
24500
24501    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24502    let (workspace, cx) =
24503        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24504    let worktree_id = workspace.update(cx, |workspace, cx| {
24505        workspace.project().update(cx, |project, cx| {
24506            project.worktrees(cx).next().unwrap().read(cx).id()
24507        })
24508    });
24509
24510    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24511    let editor = workspace
24512        .update_in(cx, |workspace, window, cx| {
24513            workspace.open_path(
24514                (worktree_id, rel_path("main.rs")),
24515                Some(pane.downgrade()),
24516                true,
24517                window,
24518                cx,
24519            )
24520        })
24521        .unwrap()
24522        .await
24523        .downcast::<Editor>()
24524        .unwrap();
24525    pane.update(cx, |pane, cx| {
24526        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24527        open_editor.update(cx, |editor, cx| {
24528            assert_eq!(
24529                editor.display_text(cx),
24530                main_text,
24531                "Original main.rs text on initial open",
24532            );
24533        })
24534    });
24535    editor.update_in(cx, |editor, window, cx| {
24536        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24537    });
24538
24539    cx.update_global(|store: &mut SettingsStore, cx| {
24540        store.update_user_settings(cx, |s| {
24541            s.workspace.restore_on_file_reopen = Some(false);
24542        });
24543    });
24544    editor.update_in(cx, |editor, window, cx| {
24545        editor.fold_ranges(
24546            vec![
24547                Point::new(1, 0)..Point::new(1, 1),
24548                Point::new(2, 0)..Point::new(2, 2),
24549                Point::new(3, 0)..Point::new(3, 3),
24550            ],
24551            false,
24552            window,
24553            cx,
24554        );
24555    });
24556    pane.update_in(cx, |pane, window, cx| {
24557        pane.close_all_items(&CloseAllItems::default(), window, cx)
24558    })
24559    .await
24560    .unwrap();
24561    pane.update(cx, |pane, _| {
24562        assert!(pane.active_item().is_none());
24563    });
24564    cx.update_global(|store: &mut SettingsStore, cx| {
24565        store.update_user_settings(cx, |s| {
24566            s.workspace.restore_on_file_reopen = Some(true);
24567        });
24568    });
24569
24570    let _editor_reopened = workspace
24571        .update_in(cx, |workspace, window, cx| {
24572            workspace.open_path(
24573                (worktree_id, rel_path("main.rs")),
24574                Some(pane.downgrade()),
24575                true,
24576                window,
24577                cx,
24578            )
24579        })
24580        .unwrap()
24581        .await
24582        .downcast::<Editor>()
24583        .unwrap();
24584    pane.update(cx, |pane, cx| {
24585        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24586        open_editor.update(cx, |editor, cx| {
24587            assert_eq!(
24588                editor.display_text(cx),
24589                main_text,
24590                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24591            );
24592        })
24593    });
24594}
24595
24596#[gpui::test]
24597async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24598    struct EmptyModalView {
24599        focus_handle: gpui::FocusHandle,
24600    }
24601    impl EventEmitter<DismissEvent> for EmptyModalView {}
24602    impl Render for EmptyModalView {
24603        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24604            div()
24605        }
24606    }
24607    impl Focusable for EmptyModalView {
24608        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24609            self.focus_handle.clone()
24610        }
24611    }
24612    impl workspace::ModalView for EmptyModalView {}
24613    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24614        EmptyModalView {
24615            focus_handle: cx.focus_handle(),
24616        }
24617    }
24618
24619    init_test(cx, |_| {});
24620
24621    let fs = FakeFs::new(cx.executor());
24622    let project = Project::test(fs, [], cx).await;
24623    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24624    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24625    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24626    let editor = cx.new_window_entity(|window, cx| {
24627        Editor::new(
24628            EditorMode::full(),
24629            buffer,
24630            Some(project.clone()),
24631            window,
24632            cx,
24633        )
24634    });
24635    workspace
24636        .update(cx, |workspace, window, cx| {
24637            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24638        })
24639        .unwrap();
24640    editor.update_in(cx, |editor, window, cx| {
24641        editor.open_context_menu(&OpenContextMenu, window, cx);
24642        assert!(editor.mouse_context_menu.is_some());
24643    });
24644    workspace
24645        .update(cx, |workspace, window, cx| {
24646            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24647        })
24648        .unwrap();
24649    cx.read(|cx| {
24650        assert!(editor.read(cx).mouse_context_menu.is_none());
24651    });
24652}
24653
24654fn set_linked_edit_ranges(
24655    opening: (Point, Point),
24656    closing: (Point, Point),
24657    editor: &mut Editor,
24658    cx: &mut Context<Editor>,
24659) {
24660    let Some((buffer, _)) = editor
24661        .buffer
24662        .read(cx)
24663        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24664    else {
24665        panic!("Failed to get buffer for selection position");
24666    };
24667    let buffer = buffer.read(cx);
24668    let buffer_id = buffer.remote_id();
24669    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24670    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24671    let mut linked_ranges = HashMap::default();
24672    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24673    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24674}
24675
24676#[gpui::test]
24677async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24678    init_test(cx, |_| {});
24679
24680    let fs = FakeFs::new(cx.executor());
24681    fs.insert_file(path!("/file.html"), Default::default())
24682        .await;
24683
24684    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24685
24686    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24687    let html_language = Arc::new(Language::new(
24688        LanguageConfig {
24689            name: "HTML".into(),
24690            matcher: LanguageMatcher {
24691                path_suffixes: vec!["html".to_string()],
24692                ..LanguageMatcher::default()
24693            },
24694            brackets: BracketPairConfig {
24695                pairs: vec![BracketPair {
24696                    start: "<".into(),
24697                    end: ">".into(),
24698                    close: true,
24699                    ..Default::default()
24700                }],
24701                ..Default::default()
24702            },
24703            ..Default::default()
24704        },
24705        Some(tree_sitter_html::LANGUAGE.into()),
24706    ));
24707    language_registry.add(html_language);
24708    let mut fake_servers = language_registry.register_fake_lsp(
24709        "HTML",
24710        FakeLspAdapter {
24711            capabilities: lsp::ServerCapabilities {
24712                completion_provider: Some(lsp::CompletionOptions {
24713                    resolve_provider: Some(true),
24714                    ..Default::default()
24715                }),
24716                ..Default::default()
24717            },
24718            ..Default::default()
24719        },
24720    );
24721
24722    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24723    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24724
24725    let worktree_id = workspace
24726        .update(cx, |workspace, _window, cx| {
24727            workspace.project().update(cx, |project, cx| {
24728                project.worktrees(cx).next().unwrap().read(cx).id()
24729            })
24730        })
24731        .unwrap();
24732    project
24733        .update(cx, |project, cx| {
24734            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24735        })
24736        .await
24737        .unwrap();
24738    let editor = workspace
24739        .update(cx, |workspace, window, cx| {
24740            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24741        })
24742        .unwrap()
24743        .await
24744        .unwrap()
24745        .downcast::<Editor>()
24746        .unwrap();
24747
24748    let fake_server = fake_servers.next().await.unwrap();
24749    editor.update_in(cx, |editor, window, cx| {
24750        editor.set_text("<ad></ad>", window, cx);
24751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24752            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24753        });
24754        set_linked_edit_ranges(
24755            (Point::new(0, 1), Point::new(0, 3)),
24756            (Point::new(0, 6), Point::new(0, 8)),
24757            editor,
24758            cx,
24759        );
24760    });
24761    let mut completion_handle =
24762        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24763            Ok(Some(lsp::CompletionResponse::Array(vec![
24764                lsp::CompletionItem {
24765                    label: "head".to_string(),
24766                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24767                        lsp::InsertReplaceEdit {
24768                            new_text: "head".to_string(),
24769                            insert: lsp::Range::new(
24770                                lsp::Position::new(0, 1),
24771                                lsp::Position::new(0, 3),
24772                            ),
24773                            replace: lsp::Range::new(
24774                                lsp::Position::new(0, 1),
24775                                lsp::Position::new(0, 3),
24776                            ),
24777                        },
24778                    )),
24779                    ..Default::default()
24780                },
24781            ])))
24782        });
24783    editor.update_in(cx, |editor, window, cx| {
24784        editor.show_completions(&ShowCompletions, window, cx);
24785    });
24786    cx.run_until_parked();
24787    completion_handle.next().await.unwrap();
24788    editor.update(cx, |editor, _| {
24789        assert!(
24790            editor.context_menu_visible(),
24791            "Completion menu should be visible"
24792        );
24793    });
24794    editor.update_in(cx, |editor, window, cx| {
24795        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24796    });
24797    cx.executor().run_until_parked();
24798    editor.update(cx, |editor, cx| {
24799        assert_eq!(editor.text(cx), "<head></head>");
24800    });
24801}
24802
24803#[gpui::test]
24804async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24805    init_test(cx, |_| {});
24806
24807    let mut cx = EditorTestContext::new(cx).await;
24808    let language = Arc::new(Language::new(
24809        LanguageConfig {
24810            name: "TSX".into(),
24811            matcher: LanguageMatcher {
24812                path_suffixes: vec!["tsx".to_string()],
24813                ..LanguageMatcher::default()
24814            },
24815            brackets: BracketPairConfig {
24816                pairs: vec![BracketPair {
24817                    start: "<".into(),
24818                    end: ">".into(),
24819                    close: true,
24820                    ..Default::default()
24821                }],
24822                ..Default::default()
24823            },
24824            linked_edit_characters: HashSet::from_iter(['.']),
24825            ..Default::default()
24826        },
24827        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24828    ));
24829    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24830
24831    // Test typing > does not extend linked pair
24832    cx.set_state("<divˇ<div></div>");
24833    cx.update_editor(|editor, _, cx| {
24834        set_linked_edit_ranges(
24835            (Point::new(0, 1), Point::new(0, 4)),
24836            (Point::new(0, 11), Point::new(0, 14)),
24837            editor,
24838            cx,
24839        );
24840    });
24841    cx.update_editor(|editor, window, cx| {
24842        editor.handle_input(">", window, cx);
24843    });
24844    cx.assert_editor_state("<div>ˇ<div></div>");
24845
24846    // Test typing . do extend linked pair
24847    cx.set_state("<Animatedˇ></Animated>");
24848    cx.update_editor(|editor, _, cx| {
24849        set_linked_edit_ranges(
24850            (Point::new(0, 1), Point::new(0, 9)),
24851            (Point::new(0, 12), Point::new(0, 20)),
24852            editor,
24853            cx,
24854        );
24855    });
24856    cx.update_editor(|editor, window, cx| {
24857        editor.handle_input(".", window, cx);
24858    });
24859    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24860    cx.update_editor(|editor, _, cx| {
24861        set_linked_edit_ranges(
24862            (Point::new(0, 1), Point::new(0, 10)),
24863            (Point::new(0, 13), Point::new(0, 21)),
24864            editor,
24865            cx,
24866        );
24867    });
24868    cx.update_editor(|editor, window, cx| {
24869        editor.handle_input("V", window, cx);
24870    });
24871    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24872}
24873
24874#[gpui::test]
24875async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24876    init_test(cx, |_| {});
24877
24878    let fs = FakeFs::new(cx.executor());
24879    fs.insert_tree(
24880        path!("/root"),
24881        json!({
24882            "a": {
24883                "main.rs": "fn main() {}",
24884            },
24885            "foo": {
24886                "bar": {
24887                    "external_file.rs": "pub mod external {}",
24888                }
24889            }
24890        }),
24891    )
24892    .await;
24893
24894    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24895    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24896    language_registry.add(rust_lang());
24897    let _fake_servers = language_registry.register_fake_lsp(
24898        "Rust",
24899        FakeLspAdapter {
24900            ..FakeLspAdapter::default()
24901        },
24902    );
24903    let (workspace, cx) =
24904        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24905    let worktree_id = workspace.update(cx, |workspace, cx| {
24906        workspace.project().update(cx, |project, cx| {
24907            project.worktrees(cx).next().unwrap().read(cx).id()
24908        })
24909    });
24910
24911    let assert_language_servers_count =
24912        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24913            project.update(cx, |project, cx| {
24914                let current = project
24915                    .lsp_store()
24916                    .read(cx)
24917                    .as_local()
24918                    .unwrap()
24919                    .language_servers
24920                    .len();
24921                assert_eq!(expected, current, "{context}");
24922            });
24923        };
24924
24925    assert_language_servers_count(
24926        0,
24927        "No servers should be running before any file is open",
24928        cx,
24929    );
24930    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24931    let main_editor = workspace
24932        .update_in(cx, |workspace, window, cx| {
24933            workspace.open_path(
24934                (worktree_id, rel_path("main.rs")),
24935                Some(pane.downgrade()),
24936                true,
24937                window,
24938                cx,
24939            )
24940        })
24941        .unwrap()
24942        .await
24943        .downcast::<Editor>()
24944        .unwrap();
24945    pane.update(cx, |pane, cx| {
24946        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24947        open_editor.update(cx, |editor, cx| {
24948            assert_eq!(
24949                editor.display_text(cx),
24950                "fn main() {}",
24951                "Original main.rs text on initial open",
24952            );
24953        });
24954        assert_eq!(open_editor, main_editor);
24955    });
24956    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24957
24958    let external_editor = workspace
24959        .update_in(cx, |workspace, window, cx| {
24960            workspace.open_abs_path(
24961                PathBuf::from("/root/foo/bar/external_file.rs"),
24962                OpenOptions::default(),
24963                window,
24964                cx,
24965            )
24966        })
24967        .await
24968        .expect("opening external file")
24969        .downcast::<Editor>()
24970        .expect("downcasted external file's open element to editor");
24971    pane.update(cx, |pane, cx| {
24972        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24973        open_editor.update(cx, |editor, cx| {
24974            assert_eq!(
24975                editor.display_text(cx),
24976                "pub mod external {}",
24977                "External file is open now",
24978            );
24979        });
24980        assert_eq!(open_editor, external_editor);
24981    });
24982    assert_language_servers_count(
24983        1,
24984        "Second, external, *.rs file should join the existing server",
24985        cx,
24986    );
24987
24988    pane.update_in(cx, |pane, window, cx| {
24989        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24990    })
24991    .await
24992    .unwrap();
24993    pane.update_in(cx, |pane, window, cx| {
24994        pane.navigate_backward(&Default::default(), window, cx);
24995    });
24996    cx.run_until_parked();
24997    pane.update(cx, |pane, cx| {
24998        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24999        open_editor.update(cx, |editor, cx| {
25000            assert_eq!(
25001                editor.display_text(cx),
25002                "pub mod external {}",
25003                "External file is open now",
25004            );
25005        });
25006    });
25007    assert_language_servers_count(
25008        1,
25009        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25010        cx,
25011    );
25012
25013    cx.update(|_, cx| {
25014        workspace::reload(cx);
25015    });
25016    assert_language_servers_count(
25017        1,
25018        "After reloading the worktree with local and external files opened, only one project should be started",
25019        cx,
25020    );
25021}
25022
25023#[gpui::test]
25024async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25025    init_test(cx, |_| {});
25026
25027    let mut cx = EditorTestContext::new(cx).await;
25028    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25029    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25030
25031    // test cursor move to start of each line on tab
25032    // for `if`, `elif`, `else`, `while`, `with` and `for`
25033    cx.set_state(indoc! {"
25034        def main():
25035        ˇ    for item in items:
25036        ˇ        while item.active:
25037        ˇ            if item.value > 10:
25038        ˇ                continue
25039        ˇ            elif item.value < 0:
25040        ˇ                break
25041        ˇ            else:
25042        ˇ                with item.context() as ctx:
25043        ˇ                    yield count
25044        ˇ        else:
25045        ˇ            log('while else')
25046        ˇ    else:
25047        ˇ        log('for else')
25048    "});
25049    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25050    cx.assert_editor_state(indoc! {"
25051        def main():
25052            ˇfor item in items:
25053                ˇwhile item.active:
25054                    ˇif item.value > 10:
25055                        ˇcontinue
25056                    ˇelif item.value < 0:
25057                        ˇbreak
25058                    ˇelse:
25059                        ˇwith item.context() as ctx:
25060                            ˇyield count
25061                ˇelse:
25062                    ˇlog('while else')
25063            ˇelse:
25064                ˇlog('for else')
25065    "});
25066    // test relative indent is preserved when tab
25067    // for `if`, `elif`, `else`, `while`, `with` and `for`
25068    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25069    cx.assert_editor_state(indoc! {"
25070        def main():
25071                ˇfor item in items:
25072                    ˇwhile item.active:
25073                        ˇif item.value > 10:
25074                            ˇcontinue
25075                        ˇelif item.value < 0:
25076                            ˇbreak
25077                        ˇelse:
25078                            ˇwith item.context() as ctx:
25079                                ˇyield count
25080                    ˇelse:
25081                        ˇlog('while else')
25082                ˇelse:
25083                    ˇlog('for else')
25084    "});
25085
25086    // test cursor move to start of each line on tab
25087    // for `try`, `except`, `else`, `finally`, `match` and `def`
25088    cx.set_state(indoc! {"
25089        def main():
25090        ˇ    try:
25091        ˇ        fetch()
25092        ˇ    except ValueError:
25093        ˇ        handle_error()
25094        ˇ    else:
25095        ˇ        match value:
25096        ˇ            case _:
25097        ˇ    finally:
25098        ˇ        def status():
25099        ˇ            return 0
25100    "});
25101    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25102    cx.assert_editor_state(indoc! {"
25103        def main():
25104            ˇtry:
25105                ˇfetch()
25106            ˇexcept ValueError:
25107                ˇhandle_error()
25108            ˇelse:
25109                ˇmatch value:
25110                    ˇcase _:
25111            ˇfinally:
25112                ˇdef status():
25113                    ˇreturn 0
25114    "});
25115    // test relative indent is preserved when tab
25116    // for `try`, `except`, `else`, `finally`, `match` and `def`
25117    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25118    cx.assert_editor_state(indoc! {"
25119        def main():
25120                ˇtry:
25121                    ˇfetch()
25122                ˇexcept ValueError:
25123                    ˇhandle_error()
25124                ˇelse:
25125                    ˇmatch value:
25126                        ˇcase _:
25127                ˇfinally:
25128                    ˇdef status():
25129                        ˇreturn 0
25130    "});
25131}
25132
25133#[gpui::test]
25134async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25135    init_test(cx, |_| {});
25136
25137    let mut cx = EditorTestContext::new(cx).await;
25138    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25139    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25140
25141    // test `else` auto outdents when typed inside `if` block
25142    cx.set_state(indoc! {"
25143        def main():
25144            if i == 2:
25145                return
25146                ˇ
25147    "});
25148    cx.update_editor(|editor, window, cx| {
25149        editor.handle_input("else:", window, cx);
25150    });
25151    cx.assert_editor_state(indoc! {"
25152        def main():
25153            if i == 2:
25154                return
25155            else:ˇ
25156    "});
25157
25158    // test `except` auto outdents when typed inside `try` block
25159    cx.set_state(indoc! {"
25160        def main():
25161            try:
25162                i = 2
25163                ˇ
25164    "});
25165    cx.update_editor(|editor, window, cx| {
25166        editor.handle_input("except:", window, cx);
25167    });
25168    cx.assert_editor_state(indoc! {"
25169        def main():
25170            try:
25171                i = 2
25172            except:ˇ
25173    "});
25174
25175    // test `else` auto outdents when typed inside `except` block
25176    cx.set_state(indoc! {"
25177        def main():
25178            try:
25179                i = 2
25180            except:
25181                j = 2
25182                ˇ
25183    "});
25184    cx.update_editor(|editor, window, cx| {
25185        editor.handle_input("else:", window, cx);
25186    });
25187    cx.assert_editor_state(indoc! {"
25188        def main():
25189            try:
25190                i = 2
25191            except:
25192                j = 2
25193            else:ˇ
25194    "});
25195
25196    // test `finally` auto outdents when typed inside `else` block
25197    cx.set_state(indoc! {"
25198        def main():
25199            try:
25200                i = 2
25201            except:
25202                j = 2
25203            else:
25204                k = 2
25205                ˇ
25206    "});
25207    cx.update_editor(|editor, window, cx| {
25208        editor.handle_input("finally:", window, cx);
25209    });
25210    cx.assert_editor_state(indoc! {"
25211        def main():
25212            try:
25213                i = 2
25214            except:
25215                j = 2
25216            else:
25217                k = 2
25218            finally:ˇ
25219    "});
25220
25221    // test `else` does not outdents when typed inside `except` block right after for block
25222    cx.set_state(indoc! {"
25223        def main():
25224            try:
25225                i = 2
25226            except:
25227                for i in range(n):
25228                    pass
25229                ˇ
25230    "});
25231    cx.update_editor(|editor, window, cx| {
25232        editor.handle_input("else:", window, cx);
25233    });
25234    cx.assert_editor_state(indoc! {"
25235        def main():
25236            try:
25237                i = 2
25238            except:
25239                for i in range(n):
25240                    pass
25241                else:ˇ
25242    "});
25243
25244    // test `finally` auto outdents when typed inside `else` block right after for block
25245    cx.set_state(indoc! {"
25246        def main():
25247            try:
25248                i = 2
25249            except:
25250                j = 2
25251            else:
25252                for i in range(n):
25253                    pass
25254                ˇ
25255    "});
25256    cx.update_editor(|editor, window, cx| {
25257        editor.handle_input("finally:", window, cx);
25258    });
25259    cx.assert_editor_state(indoc! {"
25260        def main():
25261            try:
25262                i = 2
25263            except:
25264                j = 2
25265            else:
25266                for i in range(n):
25267                    pass
25268            finally:ˇ
25269    "});
25270
25271    // test `except` outdents to inner "try" block
25272    cx.set_state(indoc! {"
25273        def main():
25274            try:
25275                i = 2
25276                if i == 2:
25277                    try:
25278                        i = 3
25279                        ˇ
25280    "});
25281    cx.update_editor(|editor, window, cx| {
25282        editor.handle_input("except:", window, cx);
25283    });
25284    cx.assert_editor_state(indoc! {"
25285        def main():
25286            try:
25287                i = 2
25288                if i == 2:
25289                    try:
25290                        i = 3
25291                    except:ˇ
25292    "});
25293
25294    // test `except` outdents to outer "try" block
25295    cx.set_state(indoc! {"
25296        def main():
25297            try:
25298                i = 2
25299                if i == 2:
25300                    try:
25301                        i = 3
25302                ˇ
25303    "});
25304    cx.update_editor(|editor, window, cx| {
25305        editor.handle_input("except:", window, cx);
25306    });
25307    cx.assert_editor_state(indoc! {"
25308        def main():
25309            try:
25310                i = 2
25311                if i == 2:
25312                    try:
25313                        i = 3
25314            except:ˇ
25315    "});
25316
25317    // test `else` stays at correct indent when typed after `for` block
25318    cx.set_state(indoc! {"
25319        def main():
25320            for i in range(10):
25321                if i == 3:
25322                    break
25323            ˇ
25324    "});
25325    cx.update_editor(|editor, window, cx| {
25326        editor.handle_input("else:", window, cx);
25327    });
25328    cx.assert_editor_state(indoc! {"
25329        def main():
25330            for i in range(10):
25331                if i == 3:
25332                    break
25333            else:ˇ
25334    "});
25335
25336    // test does not outdent on typing after line with square brackets
25337    cx.set_state(indoc! {"
25338        def f() -> list[str]:
25339            ˇ
25340    "});
25341    cx.update_editor(|editor, window, cx| {
25342        editor.handle_input("a", window, cx);
25343    });
25344    cx.assert_editor_state(indoc! {"
25345        def f() -> list[str]:
2534625347    "});
25348
25349    // test does not outdent on typing : after case keyword
25350    cx.set_state(indoc! {"
25351        match 1:
25352            caseˇ
25353    "});
25354    cx.update_editor(|editor, window, cx| {
25355        editor.handle_input(":", window, cx);
25356    });
25357    cx.assert_editor_state(indoc! {"
25358        match 1:
25359            case:ˇ
25360    "});
25361}
25362
25363#[gpui::test]
25364async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25365    init_test(cx, |_| {});
25366    update_test_language_settings(cx, |settings| {
25367        settings.defaults.extend_comment_on_newline = Some(false);
25368    });
25369    let mut cx = EditorTestContext::new(cx).await;
25370    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25371    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25372
25373    // test correct indent after newline on comment
25374    cx.set_state(indoc! {"
25375        # COMMENT:ˇ
25376    "});
25377    cx.update_editor(|editor, window, cx| {
25378        editor.newline(&Newline, window, cx);
25379    });
25380    cx.assert_editor_state(indoc! {"
25381        # COMMENT:
25382        ˇ
25383    "});
25384
25385    // test correct indent after newline in brackets
25386    cx.set_state(indoc! {"
25387        {ˇ}
25388    "});
25389    cx.update_editor(|editor, window, cx| {
25390        editor.newline(&Newline, window, cx);
25391    });
25392    cx.run_until_parked();
25393    cx.assert_editor_state(indoc! {"
25394        {
25395            ˇ
25396        }
25397    "});
25398
25399    cx.set_state(indoc! {"
25400        (ˇ)
25401    "});
25402    cx.update_editor(|editor, window, cx| {
25403        editor.newline(&Newline, window, cx);
25404    });
25405    cx.run_until_parked();
25406    cx.assert_editor_state(indoc! {"
25407        (
25408            ˇ
25409        )
25410    "});
25411
25412    // do not indent after empty lists or dictionaries
25413    cx.set_state(indoc! {"
25414        a = []ˇ
25415    "});
25416    cx.update_editor(|editor, window, cx| {
25417        editor.newline(&Newline, window, cx);
25418    });
25419    cx.run_until_parked();
25420    cx.assert_editor_state(indoc! {"
25421        a = []
25422        ˇ
25423    "});
25424}
25425
25426#[gpui::test]
25427async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25428    init_test(cx, |_| {});
25429
25430    let mut cx = EditorTestContext::new(cx).await;
25431    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25432    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25433
25434    // test cursor move to start of each line on tab
25435    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25436    cx.set_state(indoc! {"
25437        function main() {
25438        ˇ    for item in $items; do
25439        ˇ        while [ -n \"$item\" ]; do
25440        ˇ            if [ \"$value\" -gt 10 ]; then
25441        ˇ                continue
25442        ˇ            elif [ \"$value\" -lt 0 ]; then
25443        ˇ                break
25444        ˇ            else
25445        ˇ                echo \"$item\"
25446        ˇ            fi
25447        ˇ        done
25448        ˇ    done
25449        ˇ}
25450    "});
25451    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25452    cx.assert_editor_state(indoc! {"
25453        function main() {
25454            ˇfor item in $items; do
25455                ˇwhile [ -n \"$item\" ]; do
25456                    ˇif [ \"$value\" -gt 10 ]; then
25457                        ˇcontinue
25458                    ˇelif [ \"$value\" -lt 0 ]; then
25459                        ˇbreak
25460                    ˇelse
25461                        ˇecho \"$item\"
25462                    ˇfi
25463                ˇdone
25464            ˇdone
25465        ˇ}
25466    "});
25467    // test relative indent is preserved when tab
25468    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25469    cx.assert_editor_state(indoc! {"
25470        function main() {
25471                ˇfor item in $items; do
25472                    ˇwhile [ -n \"$item\" ]; do
25473                        ˇif [ \"$value\" -gt 10 ]; then
25474                            ˇcontinue
25475                        ˇelif [ \"$value\" -lt 0 ]; then
25476                            ˇbreak
25477                        ˇelse
25478                            ˇecho \"$item\"
25479                        ˇfi
25480                    ˇdone
25481                ˇdone
25482            ˇ}
25483    "});
25484
25485    // test cursor move to start of each line on tab
25486    // for `case` statement with patterns
25487    cx.set_state(indoc! {"
25488        function handle() {
25489        ˇ    case \"$1\" in
25490        ˇ        start)
25491        ˇ            echo \"a\"
25492        ˇ            ;;
25493        ˇ        stop)
25494        ˇ            echo \"b\"
25495        ˇ            ;;
25496        ˇ        *)
25497        ˇ            echo \"c\"
25498        ˇ            ;;
25499        ˇ    esac
25500        ˇ}
25501    "});
25502    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25503    cx.assert_editor_state(indoc! {"
25504        function handle() {
25505            ˇcase \"$1\" in
25506                ˇstart)
25507                    ˇecho \"a\"
25508                    ˇ;;
25509                ˇstop)
25510                    ˇecho \"b\"
25511                    ˇ;;
25512                ˇ*)
25513                    ˇecho \"c\"
25514                    ˇ;;
25515            ˇesac
25516        ˇ}
25517    "});
25518}
25519
25520#[gpui::test]
25521async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25522    init_test(cx, |_| {});
25523
25524    let mut cx = EditorTestContext::new(cx).await;
25525    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25526    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25527
25528    // test indents on comment insert
25529    cx.set_state(indoc! {"
25530        function main() {
25531        ˇ    for item in $items; do
25532        ˇ        while [ -n \"$item\" ]; do
25533        ˇ            if [ \"$value\" -gt 10 ]; then
25534        ˇ                continue
25535        ˇ            elif [ \"$value\" -lt 0 ]; then
25536        ˇ                break
25537        ˇ            else
25538        ˇ                echo \"$item\"
25539        ˇ            fi
25540        ˇ        done
25541        ˇ    done
25542        ˇ}
25543    "});
25544    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25545    cx.assert_editor_state(indoc! {"
25546        function main() {
25547        #ˇ    for item in $items; do
25548        #ˇ        while [ -n \"$item\" ]; do
25549        #ˇ            if [ \"$value\" -gt 10 ]; then
25550        #ˇ                continue
25551        #ˇ            elif [ \"$value\" -lt 0 ]; then
25552        #ˇ                break
25553        #ˇ            else
25554        #ˇ                echo \"$item\"
25555        #ˇ            fi
25556        #ˇ        done
25557        #ˇ    done
25558        #ˇ}
25559    "});
25560}
25561
25562#[gpui::test]
25563async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25564    init_test(cx, |_| {});
25565
25566    let mut cx = EditorTestContext::new(cx).await;
25567    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25568    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25569
25570    // test `else` auto outdents when typed inside `if` block
25571    cx.set_state(indoc! {"
25572        if [ \"$1\" = \"test\" ]; then
25573            echo \"foo bar\"
25574            ˇ
25575    "});
25576    cx.update_editor(|editor, window, cx| {
25577        editor.handle_input("else", window, cx);
25578    });
25579    cx.assert_editor_state(indoc! {"
25580        if [ \"$1\" = \"test\" ]; then
25581            echo \"foo bar\"
25582        elseˇ
25583    "});
25584
25585    // test `elif` auto outdents when typed inside `if` block
25586    cx.set_state(indoc! {"
25587        if [ \"$1\" = \"test\" ]; then
25588            echo \"foo bar\"
25589            ˇ
25590    "});
25591    cx.update_editor(|editor, window, cx| {
25592        editor.handle_input("elif", window, cx);
25593    });
25594    cx.assert_editor_state(indoc! {"
25595        if [ \"$1\" = \"test\" ]; then
25596            echo \"foo bar\"
25597        elifˇ
25598    "});
25599
25600    // test `fi` auto outdents when typed inside `else` block
25601    cx.set_state(indoc! {"
25602        if [ \"$1\" = \"test\" ]; then
25603            echo \"foo bar\"
25604        else
25605            echo \"bar baz\"
25606            ˇ
25607    "});
25608    cx.update_editor(|editor, window, cx| {
25609        editor.handle_input("fi", window, cx);
25610    });
25611    cx.assert_editor_state(indoc! {"
25612        if [ \"$1\" = \"test\" ]; then
25613            echo \"foo bar\"
25614        else
25615            echo \"bar baz\"
25616        fiˇ
25617    "});
25618
25619    // test `done` auto outdents when typed inside `while` block
25620    cx.set_state(indoc! {"
25621        while read line; do
25622            echo \"$line\"
25623            ˇ
25624    "});
25625    cx.update_editor(|editor, window, cx| {
25626        editor.handle_input("done", window, cx);
25627    });
25628    cx.assert_editor_state(indoc! {"
25629        while read line; do
25630            echo \"$line\"
25631        doneˇ
25632    "});
25633
25634    // test `done` auto outdents when typed inside `for` block
25635    cx.set_state(indoc! {"
25636        for file in *.txt; do
25637            cat \"$file\"
25638            ˇ
25639    "});
25640    cx.update_editor(|editor, window, cx| {
25641        editor.handle_input("done", window, cx);
25642    });
25643    cx.assert_editor_state(indoc! {"
25644        for file in *.txt; do
25645            cat \"$file\"
25646        doneˇ
25647    "});
25648
25649    // test `esac` auto outdents when typed inside `case` block
25650    cx.set_state(indoc! {"
25651        case \"$1\" in
25652            start)
25653                echo \"foo bar\"
25654                ;;
25655            stop)
25656                echo \"bar baz\"
25657                ;;
25658            ˇ
25659    "});
25660    cx.update_editor(|editor, window, cx| {
25661        editor.handle_input("esac", window, cx);
25662    });
25663    cx.assert_editor_state(indoc! {"
25664        case \"$1\" in
25665            start)
25666                echo \"foo bar\"
25667                ;;
25668            stop)
25669                echo \"bar baz\"
25670                ;;
25671        esacˇ
25672    "});
25673
25674    // test `*)` auto outdents when typed inside `case` block
25675    cx.set_state(indoc! {"
25676        case \"$1\" in
25677            start)
25678                echo \"foo bar\"
25679                ;;
25680                ˇ
25681    "});
25682    cx.update_editor(|editor, window, cx| {
25683        editor.handle_input("*)", window, cx);
25684    });
25685    cx.assert_editor_state(indoc! {"
25686        case \"$1\" in
25687            start)
25688                echo \"foo bar\"
25689                ;;
25690            *)ˇ
25691    "});
25692
25693    // test `fi` outdents to correct level with nested if blocks
25694    cx.set_state(indoc! {"
25695        if [ \"$1\" = \"test\" ]; then
25696            echo \"outer if\"
25697            if [ \"$2\" = \"debug\" ]; then
25698                echo \"inner if\"
25699                ˇ
25700    "});
25701    cx.update_editor(|editor, window, cx| {
25702        editor.handle_input("fi", window, cx);
25703    });
25704    cx.assert_editor_state(indoc! {"
25705        if [ \"$1\" = \"test\" ]; then
25706            echo \"outer if\"
25707            if [ \"$2\" = \"debug\" ]; then
25708                echo \"inner if\"
25709            fiˇ
25710    "});
25711}
25712
25713#[gpui::test]
25714async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25715    init_test(cx, |_| {});
25716    update_test_language_settings(cx, |settings| {
25717        settings.defaults.extend_comment_on_newline = Some(false);
25718    });
25719    let mut cx = EditorTestContext::new(cx).await;
25720    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25721    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25722
25723    // test correct indent after newline on comment
25724    cx.set_state(indoc! {"
25725        # COMMENT:ˇ
25726    "});
25727    cx.update_editor(|editor, window, cx| {
25728        editor.newline(&Newline, window, cx);
25729    });
25730    cx.assert_editor_state(indoc! {"
25731        # COMMENT:
25732        ˇ
25733    "});
25734
25735    // test correct indent after newline after `then`
25736    cx.set_state(indoc! {"
25737
25738        if [ \"$1\" = \"test\" ]; thenˇ
25739    "});
25740    cx.update_editor(|editor, window, cx| {
25741        editor.newline(&Newline, window, cx);
25742    });
25743    cx.run_until_parked();
25744    cx.assert_editor_state(indoc! {"
25745
25746        if [ \"$1\" = \"test\" ]; then
25747            ˇ
25748    "});
25749
25750    // test correct indent after newline after `else`
25751    cx.set_state(indoc! {"
25752        if [ \"$1\" = \"test\" ]; then
25753        elseˇ
25754    "});
25755    cx.update_editor(|editor, window, cx| {
25756        editor.newline(&Newline, window, cx);
25757    });
25758    cx.run_until_parked();
25759    cx.assert_editor_state(indoc! {"
25760        if [ \"$1\" = \"test\" ]; then
25761        else
25762            ˇ
25763    "});
25764
25765    // test correct indent after newline after `elif`
25766    cx.set_state(indoc! {"
25767        if [ \"$1\" = \"test\" ]; then
25768        elifˇ
25769    "});
25770    cx.update_editor(|editor, window, cx| {
25771        editor.newline(&Newline, window, cx);
25772    });
25773    cx.run_until_parked();
25774    cx.assert_editor_state(indoc! {"
25775        if [ \"$1\" = \"test\" ]; then
25776        elif
25777            ˇ
25778    "});
25779
25780    // test correct indent after newline after `do`
25781    cx.set_state(indoc! {"
25782        for file in *.txt; doˇ
25783    "});
25784    cx.update_editor(|editor, window, cx| {
25785        editor.newline(&Newline, window, cx);
25786    });
25787    cx.run_until_parked();
25788    cx.assert_editor_state(indoc! {"
25789        for file in *.txt; do
25790            ˇ
25791    "});
25792
25793    // test correct indent after newline after case pattern
25794    cx.set_state(indoc! {"
25795        case \"$1\" in
25796            start)ˇ
25797    "});
25798    cx.update_editor(|editor, window, cx| {
25799        editor.newline(&Newline, window, cx);
25800    });
25801    cx.run_until_parked();
25802    cx.assert_editor_state(indoc! {"
25803        case \"$1\" in
25804            start)
25805                ˇ
25806    "});
25807
25808    // test correct indent after newline after case pattern
25809    cx.set_state(indoc! {"
25810        case \"$1\" in
25811            start)
25812                ;;
25813            *)ˇ
25814    "});
25815    cx.update_editor(|editor, window, cx| {
25816        editor.newline(&Newline, window, cx);
25817    });
25818    cx.run_until_parked();
25819    cx.assert_editor_state(indoc! {"
25820        case \"$1\" in
25821            start)
25822                ;;
25823            *)
25824                ˇ
25825    "});
25826
25827    // test correct indent after newline after function opening brace
25828    cx.set_state(indoc! {"
25829        function test() {ˇ}
25830    "});
25831    cx.update_editor(|editor, window, cx| {
25832        editor.newline(&Newline, window, cx);
25833    });
25834    cx.run_until_parked();
25835    cx.assert_editor_state(indoc! {"
25836        function test() {
25837            ˇ
25838        }
25839    "});
25840
25841    // test no extra indent after semicolon on same line
25842    cx.set_state(indoc! {"
25843        echo \"test\"25844    "});
25845    cx.update_editor(|editor, window, cx| {
25846        editor.newline(&Newline, window, cx);
25847    });
25848    cx.run_until_parked();
25849    cx.assert_editor_state(indoc! {"
25850        echo \"test\";
25851        ˇ
25852    "});
25853}
25854
25855fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25856    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25857    point..point
25858}
25859
25860#[track_caller]
25861fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25862    let (text, ranges) = marked_text_ranges(marked_text, true);
25863    assert_eq!(editor.text(cx), text);
25864    assert_eq!(
25865        editor.selections.ranges(&editor.display_snapshot(cx)),
25866        ranges
25867            .iter()
25868            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
25869            .collect::<Vec<_>>(),
25870        "Assert selections are {}",
25871        marked_text
25872    );
25873}
25874
25875pub fn handle_signature_help_request(
25876    cx: &mut EditorLspTestContext,
25877    mocked_response: lsp::SignatureHelp,
25878) -> impl Future<Output = ()> + use<> {
25879    let mut request =
25880        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25881            let mocked_response = mocked_response.clone();
25882            async move { Ok(Some(mocked_response)) }
25883        });
25884
25885    async move {
25886        request.next().await;
25887    }
25888}
25889
25890#[track_caller]
25891pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25892    cx.update_editor(|editor, _, _| {
25893        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25894            let entries = menu.entries.borrow();
25895            let entries = entries
25896                .iter()
25897                .map(|entry| entry.string.as_str())
25898                .collect::<Vec<_>>();
25899            assert_eq!(entries, expected);
25900        } else {
25901            panic!("Expected completions menu");
25902        }
25903    });
25904}
25905
25906#[gpui::test]
25907async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
25908    init_test(cx, |_| {});
25909    let mut cx = EditorLspTestContext::new_rust(
25910        lsp::ServerCapabilities {
25911            completion_provider: Some(lsp::CompletionOptions {
25912                ..Default::default()
25913            }),
25914            ..Default::default()
25915        },
25916        cx,
25917    )
25918    .await;
25919    cx.lsp
25920        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25921            Ok(Some(lsp::CompletionResponse::Array(vec![
25922                lsp::CompletionItem {
25923                    label: "unsafe".into(),
25924                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25925                        range: lsp::Range {
25926                            start: lsp::Position {
25927                                line: 0,
25928                                character: 9,
25929                            },
25930                            end: lsp::Position {
25931                                line: 0,
25932                                character: 11,
25933                            },
25934                        },
25935                        new_text: "unsafe".to_string(),
25936                    })),
25937                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
25938                    ..Default::default()
25939                },
25940            ])))
25941        });
25942
25943    cx.update_editor(|editor, _, cx| {
25944        editor.project().unwrap().update(cx, |project, cx| {
25945            project.snippets().update(cx, |snippets, _cx| {
25946                snippets.add_snippet_for_test(
25947                    None,
25948                    PathBuf::from("test_snippets.json"),
25949                    vec![
25950                        Arc::new(project::snippet_provider::Snippet {
25951                            prefix: vec![
25952                                "unlimited word count".to_string(),
25953                                "unlimit word count".to_string(),
25954                                "unlimited unknown".to_string(),
25955                            ],
25956                            body: "this is many words".to_string(),
25957                            description: Some("description".to_string()),
25958                            name: "multi-word snippet test".to_string(),
25959                        }),
25960                        Arc::new(project::snippet_provider::Snippet {
25961                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
25962                            body: "fewer words".to_string(),
25963                            description: Some("alt description".to_string()),
25964                            name: "other name".to_string(),
25965                        }),
25966                        Arc::new(project::snippet_provider::Snippet {
25967                            prefix: vec!["ab aa".to_string()],
25968                            body: "abcd".to_string(),
25969                            description: None,
25970                            name: "alphabet".to_string(),
25971                        }),
25972                    ],
25973                );
25974            });
25975        })
25976    });
25977
25978    let get_completions = |cx: &mut EditorLspTestContext| {
25979        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
25980            Some(CodeContextMenu::Completions(context_menu)) => {
25981                let entries = context_menu.entries.borrow();
25982                entries
25983                    .iter()
25984                    .map(|entry| entry.string.clone())
25985                    .collect_vec()
25986            }
25987            _ => vec![],
25988        })
25989    };
25990
25991    // snippets:
25992    //  @foo
25993    //  foo bar
25994    //
25995    // when typing:
25996    //
25997    // when typing:
25998    //  - if I type a symbol "open the completions with snippets only"
25999    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26000    //
26001    // stuff we need:
26002    //  - filtering logic change?
26003    //  - remember how far back the completion started.
26004
26005    let test_cases: &[(&str, &[&str])] = &[
26006        (
26007            "un",
26008            &[
26009                "unsafe",
26010                "unlimit word count",
26011                "unlimited unknown",
26012                "unlimited word count",
26013                "unsnip",
26014            ],
26015        ),
26016        (
26017            "u ",
26018            &[
26019                "unlimit word count",
26020                "unlimited unknown",
26021                "unlimited word count",
26022            ],
26023        ),
26024        ("u a", &["ab aa", "unsafe"]), // unsAfe
26025        (
26026            "u u",
26027            &[
26028                "unsafe",
26029                "unlimit word count",
26030                "unlimited unknown", // ranked highest among snippets
26031                "unlimited word count",
26032                "unsnip",
26033            ],
26034        ),
26035        ("uw c", &["unlimit word count", "unlimited word count"]),
26036        (
26037            "u w",
26038            &[
26039                "unlimit word count",
26040                "unlimited word count",
26041                "unlimited unknown",
26042            ],
26043        ),
26044        ("u w ", &["unlimit word count", "unlimited word count"]),
26045        (
26046            "u ",
26047            &[
26048                "unlimit word count",
26049                "unlimited unknown",
26050                "unlimited word count",
26051            ],
26052        ),
26053        ("wor", &[]),
26054        ("uf", &["unsafe"]),
26055        ("af", &["unsafe"]),
26056        ("afu", &[]),
26057        (
26058            "ue",
26059            &["unsafe", "unlimited unknown", "unlimited word count"],
26060        ),
26061        ("@", &["@few"]),
26062        ("@few", &["@few"]),
26063        ("@ ", &[]),
26064        ("a@", &["@few"]),
26065        ("a@f", &["@few", "unsafe"]),
26066        ("a@fw", &["@few"]),
26067        ("a", &["ab aa", "unsafe"]),
26068        ("aa", &["ab aa"]),
26069        ("aaa", &["ab aa"]),
26070        ("ab", &["ab aa"]),
26071        ("ab ", &["ab aa"]),
26072        ("ab a", &["ab aa", "unsafe"]),
26073        ("ab ab", &["ab aa"]),
26074        ("ab ab aa", &["ab aa"]),
26075    ];
26076
26077    for &(input_to_simulate, expected_completions) in test_cases {
26078        cx.set_state("fn a() { ˇ }\n");
26079        for c in input_to_simulate.split("") {
26080            cx.simulate_input(c);
26081            cx.run_until_parked();
26082        }
26083        let expected_completions = expected_completions
26084            .iter()
26085            .map(|s| s.to_string())
26086            .collect_vec();
26087        assert_eq!(
26088            get_completions(&mut cx),
26089            expected_completions,
26090            "< actual / expected >, input = {input_to_simulate:?}",
26091        );
26092    }
26093}
26094
26095/// Handle completion request passing a marked string specifying where the completion
26096/// should be triggered from using '|' character, what range should be replaced, and what completions
26097/// should be returned using '<' and '>' to delimit the range.
26098///
26099/// Also see `handle_completion_request_with_insert_and_replace`.
26100#[track_caller]
26101pub fn handle_completion_request(
26102    marked_string: &str,
26103    completions: Vec<&'static str>,
26104    is_incomplete: bool,
26105    counter: Arc<AtomicUsize>,
26106    cx: &mut EditorLspTestContext,
26107) -> impl Future<Output = ()> {
26108    let complete_from_marker: TextRangeMarker = '|'.into();
26109    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26110    let (_, mut marked_ranges) = marked_text_ranges_by(
26111        marked_string,
26112        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26113    );
26114
26115    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26116        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26117    ));
26118    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26119    let replace_range =
26120        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26121
26122    let mut request =
26123        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26124            let completions = completions.clone();
26125            counter.fetch_add(1, atomic::Ordering::Release);
26126            async move {
26127                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26128                assert_eq!(
26129                    params.text_document_position.position,
26130                    complete_from_position
26131                );
26132                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26133                    is_incomplete,
26134                    item_defaults: None,
26135                    items: completions
26136                        .iter()
26137                        .map(|completion_text| lsp::CompletionItem {
26138                            label: completion_text.to_string(),
26139                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26140                                range: replace_range,
26141                                new_text: completion_text.to_string(),
26142                            })),
26143                            ..Default::default()
26144                        })
26145                        .collect(),
26146                })))
26147            }
26148        });
26149
26150    async move {
26151        request.next().await;
26152    }
26153}
26154
26155/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26156/// given instead, which also contains an `insert` range.
26157///
26158/// This function uses markers to define ranges:
26159/// - `|` marks the cursor position
26160/// - `<>` marks the replace range
26161/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26162pub fn handle_completion_request_with_insert_and_replace(
26163    cx: &mut EditorLspTestContext,
26164    marked_string: &str,
26165    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26166    counter: Arc<AtomicUsize>,
26167) -> impl Future<Output = ()> {
26168    let complete_from_marker: TextRangeMarker = '|'.into();
26169    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26170    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26171
26172    let (_, mut marked_ranges) = marked_text_ranges_by(
26173        marked_string,
26174        vec![
26175            complete_from_marker.clone(),
26176            replace_range_marker.clone(),
26177            insert_range_marker.clone(),
26178        ],
26179    );
26180
26181    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26182        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26183    ));
26184    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26185    let replace_range =
26186        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26187
26188    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26189        Some(ranges) if !ranges.is_empty() => {
26190            let range1 = ranges[0].clone();
26191            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26192        }
26193        _ => lsp::Range {
26194            start: replace_range.start,
26195            end: complete_from_position,
26196        },
26197    };
26198
26199    let mut request =
26200        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26201            let completions = completions.clone();
26202            counter.fetch_add(1, atomic::Ordering::Release);
26203            async move {
26204                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26205                assert_eq!(
26206                    params.text_document_position.position, complete_from_position,
26207                    "marker `|` position doesn't match",
26208                );
26209                Ok(Some(lsp::CompletionResponse::Array(
26210                    completions
26211                        .iter()
26212                        .map(|(label, new_text)| lsp::CompletionItem {
26213                            label: label.to_string(),
26214                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26215                                lsp::InsertReplaceEdit {
26216                                    insert: insert_range,
26217                                    replace: replace_range,
26218                                    new_text: new_text.to_string(),
26219                                },
26220                            )),
26221                            ..Default::default()
26222                        })
26223                        .collect(),
26224                )))
26225            }
26226        });
26227
26228    async move {
26229        request.next().await;
26230    }
26231}
26232
26233fn handle_resolve_completion_request(
26234    cx: &mut EditorLspTestContext,
26235    edits: Option<Vec<(&'static str, &'static str)>>,
26236) -> impl Future<Output = ()> {
26237    let edits = edits.map(|edits| {
26238        edits
26239            .iter()
26240            .map(|(marked_string, new_text)| {
26241                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26242                let replace_range = cx.to_lsp_range(
26243                    MultiBufferOffset(marked_ranges[0].start)
26244                        ..MultiBufferOffset(marked_ranges[0].end),
26245                );
26246                lsp::TextEdit::new(replace_range, new_text.to_string())
26247            })
26248            .collect::<Vec<_>>()
26249    });
26250
26251    let mut request =
26252        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26253            let edits = edits.clone();
26254            async move {
26255                Ok(lsp::CompletionItem {
26256                    additional_text_edits: edits,
26257                    ..Default::default()
26258                })
26259            }
26260        });
26261
26262    async move {
26263        request.next().await;
26264    }
26265}
26266
26267pub(crate) fn update_test_language_settings(
26268    cx: &mut TestAppContext,
26269    f: impl Fn(&mut AllLanguageSettingsContent),
26270) {
26271    cx.update(|cx| {
26272        SettingsStore::update_global(cx, |store, cx| {
26273            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26274        });
26275    });
26276}
26277
26278pub(crate) fn update_test_project_settings(
26279    cx: &mut TestAppContext,
26280    f: impl Fn(&mut ProjectSettingsContent),
26281) {
26282    cx.update(|cx| {
26283        SettingsStore::update_global(cx, |store, cx| {
26284            store.update_user_settings(cx, |settings| f(&mut settings.project));
26285        });
26286    });
26287}
26288
26289pub(crate) fn update_test_editor_settings(
26290    cx: &mut TestAppContext,
26291    f: impl Fn(&mut EditorSettingsContent),
26292) {
26293    cx.update(|cx| {
26294        SettingsStore::update_global(cx, |store, cx| {
26295            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26296        })
26297    })
26298}
26299
26300pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26301    cx.update(|cx| {
26302        assets::Assets.load_test_fonts(cx);
26303        let store = SettingsStore::test(cx);
26304        cx.set_global(store);
26305        theme::init(theme::LoadThemes::JustBase, cx);
26306        release_channel::init(semver::Version::new(0, 0, 0), cx);
26307        crate::init(cx);
26308    });
26309    zlog::init_test();
26310    update_test_language_settings(cx, f);
26311}
26312
26313#[track_caller]
26314fn assert_hunk_revert(
26315    not_reverted_text_with_selections: &str,
26316    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
26317    expected_reverted_text_with_selections: &str,
26318    base_text: &str,
26319    cx: &mut EditorLspTestContext,
26320) {
26321    cx.set_state(not_reverted_text_with_selections);
26322    cx.set_head_text(base_text);
26323    cx.executor().run_until_parked();
26324
26325    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
26326        let snapshot = editor.snapshot(window, cx);
26327        let reverted_hunk_statuses = snapshot
26328            .buffer_snapshot()
26329            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
26330            .map(|hunk| hunk.status().kind)
26331            .collect::<Vec<_>>();
26332
26333        editor.git_restore(&Default::default(), window, cx);
26334        reverted_hunk_statuses
26335    });
26336    cx.executor().run_until_parked();
26337    cx.assert_editor_state(expected_reverted_text_with_selections);
26338    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
26339}
26340
26341#[gpui::test(iterations = 10)]
26342async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
26343    init_test(cx, |_| {});
26344
26345    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
26346    let counter = diagnostic_requests.clone();
26347
26348    let fs = FakeFs::new(cx.executor());
26349    fs.insert_tree(
26350        path!("/a"),
26351        json!({
26352            "first.rs": "fn main() { let a = 5; }",
26353            "second.rs": "// Test file",
26354        }),
26355    )
26356    .await;
26357
26358    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26359    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26360    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26361
26362    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26363    language_registry.add(rust_lang());
26364    let mut fake_servers = language_registry.register_fake_lsp(
26365        "Rust",
26366        FakeLspAdapter {
26367            capabilities: lsp::ServerCapabilities {
26368                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
26369                    lsp::DiagnosticOptions {
26370                        identifier: None,
26371                        inter_file_dependencies: true,
26372                        workspace_diagnostics: true,
26373                        work_done_progress_options: Default::default(),
26374                    },
26375                )),
26376                ..Default::default()
26377            },
26378            ..Default::default()
26379        },
26380    );
26381
26382    let editor = workspace
26383        .update(cx, |workspace, window, cx| {
26384            workspace.open_abs_path(
26385                PathBuf::from(path!("/a/first.rs")),
26386                OpenOptions::default(),
26387                window,
26388                cx,
26389            )
26390        })
26391        .unwrap()
26392        .await
26393        .unwrap()
26394        .downcast::<Editor>()
26395        .unwrap();
26396    let fake_server = fake_servers.next().await.unwrap();
26397    let server_id = fake_server.server.server_id();
26398    let mut first_request = fake_server
26399        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
26400            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
26401            let result_id = Some(new_result_id.to_string());
26402            assert_eq!(
26403                params.text_document.uri,
26404                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26405            );
26406            async move {
26407                Ok(lsp::DocumentDiagnosticReportResult::Report(
26408                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
26409                        related_documents: None,
26410                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
26411                            items: Vec::new(),
26412                            result_id,
26413                        },
26414                    }),
26415                ))
26416            }
26417        });
26418
26419    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
26420        project.update(cx, |project, cx| {
26421            let buffer_id = editor
26422                .read(cx)
26423                .buffer()
26424                .read(cx)
26425                .as_singleton()
26426                .expect("created a singleton buffer")
26427                .read(cx)
26428                .remote_id();
26429            let buffer_result_id = project
26430                .lsp_store()
26431                .read(cx)
26432                .result_id(server_id, buffer_id, cx);
26433            assert_eq!(expected, buffer_result_id);
26434        });
26435    };
26436
26437    ensure_result_id(None, cx);
26438    cx.executor().advance_clock(Duration::from_millis(60));
26439    cx.executor().run_until_parked();
26440    assert_eq!(
26441        diagnostic_requests.load(atomic::Ordering::Acquire),
26442        1,
26443        "Opening file should trigger diagnostic request"
26444    );
26445    first_request
26446        .next()
26447        .await
26448        .expect("should have sent the first diagnostics pull request");
26449    ensure_result_id(Some("1".to_string()), cx);
26450
26451    // Editing should trigger diagnostics
26452    editor.update_in(cx, |editor, window, cx| {
26453        editor.handle_input("2", window, cx)
26454    });
26455    cx.executor().advance_clock(Duration::from_millis(60));
26456    cx.executor().run_until_parked();
26457    assert_eq!(
26458        diagnostic_requests.load(atomic::Ordering::Acquire),
26459        2,
26460        "Editing should trigger diagnostic request"
26461    );
26462    ensure_result_id(Some("2".to_string()), cx);
26463
26464    // Moving cursor should not trigger diagnostic request
26465    editor.update_in(cx, |editor, window, cx| {
26466        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26467            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
26468        });
26469    });
26470    cx.executor().advance_clock(Duration::from_millis(60));
26471    cx.executor().run_until_parked();
26472    assert_eq!(
26473        diagnostic_requests.load(atomic::Ordering::Acquire),
26474        2,
26475        "Cursor movement should not trigger diagnostic request"
26476    );
26477    ensure_result_id(Some("2".to_string()), cx);
26478    // Multiple rapid edits should be debounced
26479    for _ in 0..5 {
26480        editor.update_in(cx, |editor, window, cx| {
26481            editor.handle_input("x", window, cx)
26482        });
26483    }
26484    cx.executor().advance_clock(Duration::from_millis(60));
26485    cx.executor().run_until_parked();
26486
26487    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
26488    assert!(
26489        final_requests <= 4,
26490        "Multiple rapid edits should be debounced (got {final_requests} requests)",
26491    );
26492    ensure_result_id(Some(final_requests.to_string()), cx);
26493}
26494
26495#[gpui::test]
26496async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
26497    // Regression test for issue #11671
26498    // Previously, adding a cursor after moving multiple cursors would reset
26499    // the cursor count instead of adding to the existing cursors.
26500    init_test(cx, |_| {});
26501    let mut cx = EditorTestContext::new(cx).await;
26502
26503    // Create a simple buffer with cursor at start
26504    cx.set_state(indoc! {"
26505        ˇaaaa
26506        bbbb
26507        cccc
26508        dddd
26509        eeee
26510        ffff
26511        gggg
26512        hhhh"});
26513
26514    // Add 2 cursors below (so we have 3 total)
26515    cx.update_editor(|editor, window, cx| {
26516        editor.add_selection_below(&Default::default(), window, cx);
26517        editor.add_selection_below(&Default::default(), window, cx);
26518    });
26519
26520    // Verify we have 3 cursors
26521    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
26522    assert_eq!(
26523        initial_count, 3,
26524        "Should have 3 cursors after adding 2 below"
26525    );
26526
26527    // Move down one line
26528    cx.update_editor(|editor, window, cx| {
26529        editor.move_down(&MoveDown, window, cx);
26530    });
26531
26532    // Add another cursor below
26533    cx.update_editor(|editor, window, cx| {
26534        editor.add_selection_below(&Default::default(), window, cx);
26535    });
26536
26537    // Should now have 4 cursors (3 original + 1 new)
26538    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
26539    assert_eq!(
26540        final_count, 4,
26541        "Should have 4 cursors after moving and adding another"
26542    );
26543}
26544
26545#[gpui::test]
26546async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
26547    init_test(cx, |_| {});
26548
26549    let mut cx = EditorTestContext::new(cx).await;
26550
26551    cx.set_state(indoc!(
26552        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
26553           Second line here"#
26554    ));
26555
26556    cx.update_editor(|editor, window, cx| {
26557        // Enable soft wrapping with a narrow width to force soft wrapping and
26558        // confirm that more than 2 rows are being displayed.
26559        editor.set_wrap_width(Some(100.0.into()), cx);
26560        assert!(editor.display_text(cx).lines().count() > 2);
26561
26562        editor.add_selection_below(
26563            &AddSelectionBelow {
26564                skip_soft_wrap: true,
26565            },
26566            window,
26567            cx,
26568        );
26569
26570        assert_eq!(
26571            display_ranges(editor, cx),
26572            &[
26573                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26574                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
26575            ]
26576        );
26577
26578        editor.add_selection_above(
26579            &AddSelectionAbove {
26580                skip_soft_wrap: true,
26581            },
26582            window,
26583            cx,
26584        );
26585
26586        assert_eq!(
26587            display_ranges(editor, cx),
26588            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26589        );
26590
26591        editor.add_selection_below(
26592            &AddSelectionBelow {
26593                skip_soft_wrap: false,
26594            },
26595            window,
26596            cx,
26597        );
26598
26599        assert_eq!(
26600            display_ranges(editor, cx),
26601            &[
26602                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26603                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26604            ]
26605        );
26606
26607        editor.add_selection_above(
26608            &AddSelectionAbove {
26609                skip_soft_wrap: false,
26610            },
26611            window,
26612            cx,
26613        );
26614
26615        assert_eq!(
26616            display_ranges(editor, cx),
26617            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26618        );
26619    });
26620}
26621
26622#[gpui::test(iterations = 10)]
26623async fn test_document_colors(cx: &mut TestAppContext) {
26624    let expected_color = Rgba {
26625        r: 0.33,
26626        g: 0.33,
26627        b: 0.33,
26628        a: 0.33,
26629    };
26630
26631    init_test(cx, |_| {});
26632
26633    let fs = FakeFs::new(cx.executor());
26634    fs.insert_tree(
26635        path!("/a"),
26636        json!({
26637            "first.rs": "fn main() { let a = 5; }",
26638        }),
26639    )
26640    .await;
26641
26642    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26643    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26644    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26645
26646    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26647    language_registry.add(rust_lang());
26648    let mut fake_servers = language_registry.register_fake_lsp(
26649        "Rust",
26650        FakeLspAdapter {
26651            capabilities: lsp::ServerCapabilities {
26652                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26653                ..lsp::ServerCapabilities::default()
26654            },
26655            name: "rust-analyzer",
26656            ..FakeLspAdapter::default()
26657        },
26658    );
26659    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26660        "Rust",
26661        FakeLspAdapter {
26662            capabilities: lsp::ServerCapabilities {
26663                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26664                ..lsp::ServerCapabilities::default()
26665            },
26666            name: "not-rust-analyzer",
26667            ..FakeLspAdapter::default()
26668        },
26669    );
26670
26671    let editor = workspace
26672        .update(cx, |workspace, window, cx| {
26673            workspace.open_abs_path(
26674                PathBuf::from(path!("/a/first.rs")),
26675                OpenOptions::default(),
26676                window,
26677                cx,
26678            )
26679        })
26680        .unwrap()
26681        .await
26682        .unwrap()
26683        .downcast::<Editor>()
26684        .unwrap();
26685    let fake_language_server = fake_servers.next().await.unwrap();
26686    let fake_language_server_without_capabilities =
26687        fake_servers_without_capabilities.next().await.unwrap();
26688    let requests_made = Arc::new(AtomicUsize::new(0));
26689    let closure_requests_made = Arc::clone(&requests_made);
26690    let mut color_request_handle = fake_language_server
26691        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26692            let requests_made = Arc::clone(&closure_requests_made);
26693            async move {
26694                assert_eq!(
26695                    params.text_document.uri,
26696                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26697                );
26698                requests_made.fetch_add(1, atomic::Ordering::Release);
26699                Ok(vec![
26700                    lsp::ColorInformation {
26701                        range: lsp::Range {
26702                            start: lsp::Position {
26703                                line: 0,
26704                                character: 0,
26705                            },
26706                            end: lsp::Position {
26707                                line: 0,
26708                                character: 1,
26709                            },
26710                        },
26711                        color: lsp::Color {
26712                            red: 0.33,
26713                            green: 0.33,
26714                            blue: 0.33,
26715                            alpha: 0.33,
26716                        },
26717                    },
26718                    lsp::ColorInformation {
26719                        range: lsp::Range {
26720                            start: lsp::Position {
26721                                line: 0,
26722                                character: 0,
26723                            },
26724                            end: lsp::Position {
26725                                line: 0,
26726                                character: 1,
26727                            },
26728                        },
26729                        color: lsp::Color {
26730                            red: 0.33,
26731                            green: 0.33,
26732                            blue: 0.33,
26733                            alpha: 0.33,
26734                        },
26735                    },
26736                ])
26737            }
26738        });
26739
26740    let _handle = fake_language_server_without_capabilities
26741        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26742            panic!("Should not be called");
26743        });
26744    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26745    color_request_handle.next().await.unwrap();
26746    cx.run_until_parked();
26747    assert_eq!(
26748        1,
26749        requests_made.load(atomic::Ordering::Acquire),
26750        "Should query for colors once per editor open"
26751    );
26752    editor.update_in(cx, |editor, _, cx| {
26753        assert_eq!(
26754            vec![expected_color],
26755            extract_color_inlays(editor, cx),
26756            "Should have an initial inlay"
26757        );
26758    });
26759
26760    // opening another file in a split should not influence the LSP query counter
26761    workspace
26762        .update(cx, |workspace, window, cx| {
26763            assert_eq!(
26764                workspace.panes().len(),
26765                1,
26766                "Should have one pane with one editor"
26767            );
26768            workspace.move_item_to_pane_in_direction(
26769                &MoveItemToPaneInDirection {
26770                    direction: SplitDirection::Right,
26771                    focus: false,
26772                    clone: true,
26773                },
26774                window,
26775                cx,
26776            );
26777        })
26778        .unwrap();
26779    cx.run_until_parked();
26780    workspace
26781        .update(cx, |workspace, _, cx| {
26782            let panes = workspace.panes();
26783            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26784            for pane in panes {
26785                let editor = pane
26786                    .read(cx)
26787                    .active_item()
26788                    .and_then(|item| item.downcast::<Editor>())
26789                    .expect("Should have opened an editor in each split");
26790                let editor_file = editor
26791                    .read(cx)
26792                    .buffer()
26793                    .read(cx)
26794                    .as_singleton()
26795                    .expect("test deals with singleton buffers")
26796                    .read(cx)
26797                    .file()
26798                    .expect("test buffese should have a file")
26799                    .path();
26800                assert_eq!(
26801                    editor_file.as_ref(),
26802                    rel_path("first.rs"),
26803                    "Both editors should be opened for the same file"
26804                )
26805            }
26806        })
26807        .unwrap();
26808
26809    cx.executor().advance_clock(Duration::from_millis(500));
26810    let save = editor.update_in(cx, |editor, window, cx| {
26811        editor.move_to_end(&MoveToEnd, window, cx);
26812        editor.handle_input("dirty", window, cx);
26813        editor.save(
26814            SaveOptions {
26815                format: true,
26816                autosave: true,
26817            },
26818            project.clone(),
26819            window,
26820            cx,
26821        )
26822    });
26823    save.await.unwrap();
26824
26825    color_request_handle.next().await.unwrap();
26826    cx.run_until_parked();
26827    assert_eq!(
26828        2,
26829        requests_made.load(atomic::Ordering::Acquire),
26830        "Should query for colors once per save (deduplicated) and once per formatting after save"
26831    );
26832
26833    drop(editor);
26834    let close = workspace
26835        .update(cx, |workspace, window, cx| {
26836            workspace.active_pane().update(cx, |pane, cx| {
26837                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26838            })
26839        })
26840        .unwrap();
26841    close.await.unwrap();
26842    let close = workspace
26843        .update(cx, |workspace, window, cx| {
26844            workspace.active_pane().update(cx, |pane, cx| {
26845                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26846            })
26847        })
26848        .unwrap();
26849    close.await.unwrap();
26850    assert_eq!(
26851        2,
26852        requests_made.load(atomic::Ordering::Acquire),
26853        "After saving and closing all editors, no extra requests should be made"
26854    );
26855    workspace
26856        .update(cx, |workspace, _, cx| {
26857            assert!(
26858                workspace.active_item(cx).is_none(),
26859                "Should close all editors"
26860            )
26861        })
26862        .unwrap();
26863
26864    workspace
26865        .update(cx, |workspace, window, cx| {
26866            workspace.active_pane().update(cx, |pane, cx| {
26867                pane.navigate_backward(&workspace::GoBack, window, cx);
26868            })
26869        })
26870        .unwrap();
26871    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26872    cx.run_until_parked();
26873    let editor = workspace
26874        .update(cx, |workspace, _, cx| {
26875            workspace
26876                .active_item(cx)
26877                .expect("Should have reopened the editor again after navigating back")
26878                .downcast::<Editor>()
26879                .expect("Should be an editor")
26880        })
26881        .unwrap();
26882
26883    assert_eq!(
26884        2,
26885        requests_made.load(atomic::Ordering::Acquire),
26886        "Cache should be reused on buffer close and reopen"
26887    );
26888    editor.update(cx, |editor, cx| {
26889        assert_eq!(
26890            vec![expected_color],
26891            extract_color_inlays(editor, cx),
26892            "Should have an initial inlay"
26893        );
26894    });
26895
26896    drop(color_request_handle);
26897    let closure_requests_made = Arc::clone(&requests_made);
26898    let mut empty_color_request_handle = fake_language_server
26899        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26900            let requests_made = Arc::clone(&closure_requests_made);
26901            async move {
26902                assert_eq!(
26903                    params.text_document.uri,
26904                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26905                );
26906                requests_made.fetch_add(1, atomic::Ordering::Release);
26907                Ok(Vec::new())
26908            }
26909        });
26910    let save = editor.update_in(cx, |editor, window, cx| {
26911        editor.move_to_end(&MoveToEnd, window, cx);
26912        editor.handle_input("dirty_again", window, cx);
26913        editor.save(
26914            SaveOptions {
26915                format: false,
26916                autosave: true,
26917            },
26918            project.clone(),
26919            window,
26920            cx,
26921        )
26922    });
26923    save.await.unwrap();
26924
26925    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26926    empty_color_request_handle.next().await.unwrap();
26927    cx.run_until_parked();
26928    assert_eq!(
26929        3,
26930        requests_made.load(atomic::Ordering::Acquire),
26931        "Should query for colors once per save only, as formatting was not requested"
26932    );
26933    editor.update(cx, |editor, cx| {
26934        assert_eq!(
26935            Vec::<Rgba>::new(),
26936            extract_color_inlays(editor, cx),
26937            "Should clear all colors when the server returns an empty response"
26938        );
26939    });
26940}
26941
26942#[gpui::test]
26943async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26944    init_test(cx, |_| {});
26945    let (editor, cx) = cx.add_window_view(Editor::single_line);
26946    editor.update_in(cx, |editor, window, cx| {
26947        editor.set_text("oops\n\nwow\n", window, cx)
26948    });
26949    cx.run_until_parked();
26950    editor.update(cx, |editor, cx| {
26951        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26952    });
26953    editor.update(cx, |editor, cx| {
26954        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
26955    });
26956    cx.run_until_parked();
26957    editor.update(cx, |editor, cx| {
26958        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26959    });
26960}
26961
26962#[gpui::test]
26963async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26964    init_test(cx, |_| {});
26965
26966    cx.update(|cx| {
26967        register_project_item::<Editor>(cx);
26968    });
26969
26970    let fs = FakeFs::new(cx.executor());
26971    fs.insert_tree("/root1", json!({})).await;
26972    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26973        .await;
26974
26975    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26976    let (workspace, cx) =
26977        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26978
26979    let worktree_id = project.update(cx, |project, cx| {
26980        project.worktrees(cx).next().unwrap().read(cx).id()
26981    });
26982
26983    let handle = workspace
26984        .update_in(cx, |workspace, window, cx| {
26985            let project_path = (worktree_id, rel_path("one.pdf"));
26986            workspace.open_path(project_path, None, true, window, cx)
26987        })
26988        .await
26989        .unwrap();
26990
26991    assert_eq!(
26992        handle.to_any_view().entity_type(),
26993        TypeId::of::<InvalidItemView>()
26994    );
26995}
26996
26997#[gpui::test]
26998async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26999    init_test(cx, |_| {});
27000
27001    let language = Arc::new(Language::new(
27002        LanguageConfig::default(),
27003        Some(tree_sitter_rust::LANGUAGE.into()),
27004    ));
27005
27006    // Test hierarchical sibling navigation
27007    let text = r#"
27008        fn outer() {
27009            if condition {
27010                let a = 1;
27011            }
27012            let b = 2;
27013        }
27014
27015        fn another() {
27016            let c = 3;
27017        }
27018    "#;
27019
27020    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27021    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27022    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27023
27024    // Wait for parsing to complete
27025    editor
27026        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27027        .await;
27028
27029    editor.update_in(cx, |editor, window, cx| {
27030        // Start by selecting "let a = 1;" inside the if block
27031        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27032            s.select_display_ranges([
27033                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27034            ]);
27035        });
27036
27037        let initial_selection = editor
27038            .selections
27039            .display_ranges(&editor.display_snapshot(cx));
27040        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27041
27042        // Test select next sibling - should move up levels to find the next sibling
27043        // Since "let a = 1;" has no siblings in the if block, it should move up
27044        // to find "let b = 2;" which is a sibling of the if block
27045        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27046        let next_selection = editor
27047            .selections
27048            .display_ranges(&editor.display_snapshot(cx));
27049
27050        // Should have a selection and it should be different from the initial
27051        assert_eq!(
27052            next_selection.len(),
27053            1,
27054            "Should have one selection after next"
27055        );
27056        assert_ne!(
27057            next_selection[0], initial_selection[0],
27058            "Next sibling selection should be different"
27059        );
27060
27061        // Test hierarchical navigation by going to the end of the current function
27062        // and trying to navigate to the next function
27063        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27064            s.select_display_ranges([
27065                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27066            ]);
27067        });
27068
27069        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27070        let function_next_selection = editor
27071            .selections
27072            .display_ranges(&editor.display_snapshot(cx));
27073
27074        // Should move to the next function
27075        assert_eq!(
27076            function_next_selection.len(),
27077            1,
27078            "Should have one selection after function next"
27079        );
27080
27081        // Test select previous sibling navigation
27082        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27083        let prev_selection = editor
27084            .selections
27085            .display_ranges(&editor.display_snapshot(cx));
27086
27087        // Should have a selection and it should be different
27088        assert_eq!(
27089            prev_selection.len(),
27090            1,
27091            "Should have one selection after prev"
27092        );
27093        assert_ne!(
27094            prev_selection[0], function_next_selection[0],
27095            "Previous sibling selection should be different from next"
27096        );
27097    });
27098}
27099
27100#[gpui::test]
27101async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27102    init_test(cx, |_| {});
27103
27104    let mut cx = EditorTestContext::new(cx).await;
27105    cx.set_state(
27106        "let ˇvariable = 42;
27107let another = variable + 1;
27108let result = variable * 2;",
27109    );
27110
27111    // Set up document highlights manually (simulating LSP response)
27112    cx.update_editor(|editor, _window, cx| {
27113        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27114
27115        // Create highlights for "variable" occurrences
27116        let highlight_ranges = [
27117            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27118            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27119            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27120        ];
27121
27122        let anchor_ranges: Vec<_> = highlight_ranges
27123            .iter()
27124            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27125            .collect();
27126
27127        editor.highlight_background::<DocumentHighlightRead>(
27128            &anchor_ranges,
27129            |theme| theme.colors().editor_document_highlight_read_background,
27130            cx,
27131        );
27132    });
27133
27134    // Go to next highlight - should move to second "variable"
27135    cx.update_editor(|editor, window, cx| {
27136        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27137    });
27138    cx.assert_editor_state(
27139        "let variable = 42;
27140let another = ˇvariable + 1;
27141let result = variable * 2;",
27142    );
27143
27144    // Go to next highlight - should move to third "variable"
27145    cx.update_editor(|editor, window, cx| {
27146        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27147    });
27148    cx.assert_editor_state(
27149        "let variable = 42;
27150let another = variable + 1;
27151let result = ˇvariable * 2;",
27152    );
27153
27154    // Go to next highlight - should stay at third "variable" (no wrap-around)
27155    cx.update_editor(|editor, window, cx| {
27156        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27157    });
27158    cx.assert_editor_state(
27159        "let variable = 42;
27160let another = variable + 1;
27161let result = ˇvariable * 2;",
27162    );
27163
27164    // Now test going backwards from third position
27165    cx.update_editor(|editor, window, cx| {
27166        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27167    });
27168    cx.assert_editor_state(
27169        "let variable = 42;
27170let another = ˇvariable + 1;
27171let result = variable * 2;",
27172    );
27173
27174    // Go to previous highlight - should move to first "variable"
27175    cx.update_editor(|editor, window, cx| {
27176        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27177    });
27178    cx.assert_editor_state(
27179        "let ˇvariable = 42;
27180let another = variable + 1;
27181let result = variable * 2;",
27182    );
27183
27184    // Go to previous highlight - should stay on first "variable"
27185    cx.update_editor(|editor, window, cx| {
27186        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27187    });
27188    cx.assert_editor_state(
27189        "let ˇvariable = 42;
27190let another = variable + 1;
27191let result = variable * 2;",
27192    );
27193}
27194
27195#[gpui::test]
27196async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
27197    cx: &mut gpui::TestAppContext,
27198) {
27199    init_test(cx, |_| {});
27200
27201    let url = "https://zed.dev";
27202
27203    let markdown_language = Arc::new(Language::new(
27204        LanguageConfig {
27205            name: "Markdown".into(),
27206            ..LanguageConfig::default()
27207        },
27208        None,
27209    ));
27210
27211    let mut cx = EditorTestContext::new(cx).await;
27212    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27213    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27214
27215    cx.update_editor(|editor, window, cx| {
27216        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27217        editor.paste(&Paste, window, cx);
27218    });
27219
27220    cx.assert_editor_state(&format!(
27221        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27222    ));
27223}
27224
27225#[gpui::test]
27226async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
27227    cx: &mut gpui::TestAppContext,
27228) {
27229    init_test(cx, |_| {});
27230
27231    let url = "https://zed.dev";
27232
27233    let markdown_language = Arc::new(Language::new(
27234        LanguageConfig {
27235            name: "Markdown".into(),
27236            ..LanguageConfig::default()
27237        },
27238        None,
27239    ));
27240
27241    let mut cx = EditorTestContext::new(cx).await;
27242    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27243    cx.set_state(&format!(
27244        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
27245    ));
27246
27247    cx.update_editor(|editor, window, cx| {
27248        editor.copy(&Copy, window, cx);
27249    });
27250
27251    cx.set_state(&format!(
27252        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
27253    ));
27254
27255    cx.update_editor(|editor, window, cx| {
27256        editor.paste(&Paste, window, cx);
27257    });
27258
27259    cx.assert_editor_state(&format!(
27260        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
27261    ));
27262}
27263
27264#[gpui::test]
27265async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
27266    cx: &mut gpui::TestAppContext,
27267) {
27268    init_test(cx, |_| {});
27269
27270    let url = "https://zed.dev";
27271
27272    let markdown_language = Arc::new(Language::new(
27273        LanguageConfig {
27274            name: "Markdown".into(),
27275            ..LanguageConfig::default()
27276        },
27277        None,
27278    ));
27279
27280    let mut cx = EditorTestContext::new(cx).await;
27281    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27282    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
27283
27284    cx.update_editor(|editor, window, cx| {
27285        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27286        editor.paste(&Paste, window, cx);
27287    });
27288
27289    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
27290}
27291
27292#[gpui::test]
27293async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
27294    cx: &mut gpui::TestAppContext,
27295) {
27296    init_test(cx, |_| {});
27297
27298    let text = "Awesome";
27299
27300    let markdown_language = Arc::new(Language::new(
27301        LanguageConfig {
27302            name: "Markdown".into(),
27303            ..LanguageConfig::default()
27304        },
27305        None,
27306    ));
27307
27308    let mut cx = EditorTestContext::new(cx).await;
27309    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27310    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
27311
27312    cx.update_editor(|editor, window, cx| {
27313        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
27314        editor.paste(&Paste, window, cx);
27315    });
27316
27317    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
27318}
27319
27320#[gpui::test]
27321async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
27322    cx: &mut gpui::TestAppContext,
27323) {
27324    init_test(cx, |_| {});
27325
27326    let url = "https://zed.dev";
27327
27328    let markdown_language = Arc::new(Language::new(
27329        LanguageConfig {
27330            name: "Rust".into(),
27331            ..LanguageConfig::default()
27332        },
27333        None,
27334    ));
27335
27336    let mut cx = EditorTestContext::new(cx).await;
27337    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27338    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
27339
27340    cx.update_editor(|editor, window, cx| {
27341        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27342        editor.paste(&Paste, window, cx);
27343    });
27344
27345    cx.assert_editor_state(&format!(
27346        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
27347    ));
27348}
27349
27350#[gpui::test]
27351async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
27352    cx: &mut TestAppContext,
27353) {
27354    init_test(cx, |_| {});
27355
27356    let url = "https://zed.dev";
27357
27358    let markdown_language = Arc::new(Language::new(
27359        LanguageConfig {
27360            name: "Markdown".into(),
27361            ..LanguageConfig::default()
27362        },
27363        None,
27364    ));
27365
27366    let (editor, cx) = cx.add_window_view(|window, cx| {
27367        let multi_buffer = MultiBuffer::build_multi(
27368            [
27369                ("this will embed -> link", vec![Point::row_range(0..1)]),
27370                ("this will replace -> link", vec![Point::row_range(0..1)]),
27371            ],
27372            cx,
27373        );
27374        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
27375        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27376            s.select_ranges(vec![
27377                Point::new(0, 19)..Point::new(0, 23),
27378                Point::new(1, 21)..Point::new(1, 25),
27379            ])
27380        });
27381        let first_buffer_id = multi_buffer
27382            .read(cx)
27383            .excerpt_buffer_ids()
27384            .into_iter()
27385            .next()
27386            .unwrap();
27387        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
27388        first_buffer.update(cx, |buffer, cx| {
27389            buffer.set_language(Some(markdown_language.clone()), cx);
27390        });
27391
27392        editor
27393    });
27394    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
27395
27396    cx.update_editor(|editor, window, cx| {
27397        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27398        editor.paste(&Paste, window, cx);
27399    });
27400
27401    cx.assert_editor_state(&format!(
27402        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
27403    ));
27404}
27405
27406#[gpui::test]
27407async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
27408    init_test(cx, |_| {});
27409
27410    let fs = FakeFs::new(cx.executor());
27411    fs.insert_tree(
27412        path!("/project"),
27413        json!({
27414            "first.rs": "# First Document\nSome content here.",
27415            "second.rs": "Plain text content for second file.",
27416        }),
27417    )
27418    .await;
27419
27420    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
27421    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27422    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27423
27424    let language = rust_lang();
27425    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27426    language_registry.add(language.clone());
27427    let mut fake_servers = language_registry.register_fake_lsp(
27428        "Rust",
27429        FakeLspAdapter {
27430            ..FakeLspAdapter::default()
27431        },
27432    );
27433
27434    let buffer1 = project
27435        .update(cx, |project, cx| {
27436            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
27437        })
27438        .await
27439        .unwrap();
27440    let buffer2 = project
27441        .update(cx, |project, cx| {
27442            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
27443        })
27444        .await
27445        .unwrap();
27446
27447    let multi_buffer = cx.new(|cx| {
27448        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
27449        multi_buffer.set_excerpts_for_path(
27450            PathKey::for_buffer(&buffer1, cx),
27451            buffer1.clone(),
27452            [Point::zero()..buffer1.read(cx).max_point()],
27453            3,
27454            cx,
27455        );
27456        multi_buffer.set_excerpts_for_path(
27457            PathKey::for_buffer(&buffer2, cx),
27458            buffer2.clone(),
27459            [Point::zero()..buffer1.read(cx).max_point()],
27460            3,
27461            cx,
27462        );
27463        multi_buffer
27464    });
27465
27466    let (editor, cx) = cx.add_window_view(|window, cx| {
27467        Editor::new(
27468            EditorMode::full(),
27469            multi_buffer,
27470            Some(project.clone()),
27471            window,
27472            cx,
27473        )
27474    });
27475
27476    let fake_language_server = fake_servers.next().await.unwrap();
27477
27478    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
27479
27480    let save = editor.update_in(cx, |editor, window, cx| {
27481        assert!(editor.is_dirty(cx));
27482
27483        editor.save(
27484            SaveOptions {
27485                format: true,
27486                autosave: true,
27487            },
27488            project,
27489            window,
27490            cx,
27491        )
27492    });
27493    let (start_edit_tx, start_edit_rx) = oneshot::channel();
27494    let (done_edit_tx, done_edit_rx) = oneshot::channel();
27495    let mut done_edit_rx = Some(done_edit_rx);
27496    let mut start_edit_tx = Some(start_edit_tx);
27497
27498    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
27499        start_edit_tx.take().unwrap().send(()).unwrap();
27500        let done_edit_rx = done_edit_rx.take().unwrap();
27501        async move {
27502            done_edit_rx.await.unwrap();
27503            Ok(None)
27504        }
27505    });
27506
27507    start_edit_rx.await.unwrap();
27508    buffer2
27509        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
27510        .unwrap();
27511
27512    done_edit_tx.send(()).unwrap();
27513
27514    save.await.unwrap();
27515    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
27516}
27517
27518#[track_caller]
27519fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
27520    editor
27521        .all_inlays(cx)
27522        .into_iter()
27523        .filter_map(|inlay| inlay.get_color())
27524        .map(Rgba::from)
27525        .collect()
27526}
27527
27528#[gpui::test]
27529fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
27530    init_test(cx, |_| {});
27531
27532    let editor = cx.add_window(|window, cx| {
27533        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
27534        build_editor(buffer, window, cx)
27535    });
27536
27537    editor
27538        .update(cx, |editor, window, cx| {
27539            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27540                s.select_display_ranges([
27541                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
27542                ])
27543            });
27544
27545            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
27546
27547            assert_eq!(
27548                editor.display_text(cx),
27549                "line1\nline2\nline2",
27550                "Duplicating last line upward should create duplicate above, not on same line"
27551            );
27552
27553            assert_eq!(
27554                editor
27555                    .selections
27556                    .display_ranges(&editor.display_snapshot(cx)),
27557                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
27558                "Selection should move to the duplicated line"
27559            );
27560        })
27561        .unwrap();
27562}
27563
27564#[gpui::test]
27565async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
27566    init_test(cx, |_| {});
27567
27568    let mut cx = EditorTestContext::new(cx).await;
27569
27570    cx.set_state("line1\nline2ˇ");
27571
27572    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27573
27574    let clipboard_text = cx
27575        .read_from_clipboard()
27576        .and_then(|item| item.text().as_deref().map(str::to_string));
27577
27578    assert_eq!(
27579        clipboard_text,
27580        Some("line2\n".to_string()),
27581        "Copying a line without trailing newline should include a newline"
27582    );
27583
27584    cx.set_state("line1\nˇ");
27585
27586    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27587
27588    cx.assert_editor_state("line1\nline2\nˇ");
27589}
27590
27591#[gpui::test]
27592async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27593    init_test(cx, |_| {});
27594
27595    let mut cx = EditorTestContext::new(cx).await;
27596
27597    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27598
27599    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27600
27601    let clipboard_text = cx
27602        .read_from_clipboard()
27603        .and_then(|item| item.text().as_deref().map(str::to_string));
27604
27605    assert_eq!(
27606        clipboard_text,
27607        Some("line1\nline2\nline3\n".to_string()),
27608        "Copying multiple lines should include a single newline between lines"
27609    );
27610
27611    cx.set_state("lineA\nˇ");
27612
27613    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27614
27615    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27616}
27617
27618#[gpui::test]
27619async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27620    init_test(cx, |_| {});
27621
27622    let mut cx = EditorTestContext::new(cx).await;
27623
27624    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27625
27626    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
27627
27628    let clipboard_text = cx
27629        .read_from_clipboard()
27630        .and_then(|item| item.text().as_deref().map(str::to_string));
27631
27632    assert_eq!(
27633        clipboard_text,
27634        Some("line1\nline2\nline3\n".to_string()),
27635        "Copying multiple lines should include a single newline between lines"
27636    );
27637
27638    cx.set_state("lineA\nˇ");
27639
27640    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27641
27642    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27643}
27644
27645#[gpui::test]
27646async fn test_end_of_editor_context(cx: &mut TestAppContext) {
27647    init_test(cx, |_| {});
27648
27649    let mut cx = EditorTestContext::new(cx).await;
27650
27651    cx.set_state("line1\nline2ˇ");
27652    cx.update_editor(|e, window, cx| {
27653        e.set_mode(EditorMode::SingleLine);
27654        assert!(e.key_context(window, cx).contains("end_of_input"));
27655    });
27656    cx.set_state("ˇline1\nline2");
27657    cx.update_editor(|e, window, cx| {
27658        assert!(!e.key_context(window, cx).contains("end_of_input"));
27659    });
27660    cx.set_state("line1ˇ\nline2");
27661    cx.update_editor(|e, window, cx| {
27662        assert!(!e.key_context(window, cx).contains("end_of_input"));
27663    });
27664}
27665
27666#[gpui::test]
27667async fn test_sticky_scroll(cx: &mut TestAppContext) {
27668    init_test(cx, |_| {});
27669    let mut cx = EditorTestContext::new(cx).await;
27670
27671    let buffer = indoc! {"
27672            ˇfn foo() {
27673                let abc = 123;
27674            }
27675            struct Bar;
27676            impl Bar {
27677                fn new() -> Self {
27678                    Self
27679                }
27680            }
27681            fn baz() {
27682            }
27683        "};
27684    cx.set_state(&buffer);
27685
27686    cx.update_editor(|e, _, cx| {
27687        e.buffer()
27688            .read(cx)
27689            .as_singleton()
27690            .unwrap()
27691            .update(cx, |buffer, cx| {
27692                buffer.set_language(Some(rust_lang()), cx);
27693            })
27694    });
27695
27696    let mut sticky_headers = |offset: ScrollOffset| {
27697        cx.update_editor(|e, window, cx| {
27698            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
27699            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), cx)
27700                .into_iter()
27701                .map(
27702                    |StickyHeader {
27703                         start_point,
27704                         offset,
27705                         ..
27706                     }| { (start_point, offset) },
27707                )
27708                .collect::<Vec<_>>()
27709        })
27710    };
27711
27712    let fn_foo = Point { row: 0, column: 0 };
27713    let impl_bar = Point { row: 4, column: 0 };
27714    let fn_new = Point { row: 5, column: 4 };
27715
27716    assert_eq!(sticky_headers(0.0), vec![]);
27717    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
27718    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
27719    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
27720    assert_eq!(sticky_headers(2.0), vec![]);
27721    assert_eq!(sticky_headers(2.5), vec![]);
27722    assert_eq!(sticky_headers(3.0), vec![]);
27723    assert_eq!(sticky_headers(3.5), vec![]);
27724    assert_eq!(sticky_headers(4.0), vec![]);
27725    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27726    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27727    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
27728    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
27729    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
27730    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
27731    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
27732    assert_eq!(sticky_headers(8.0), vec![]);
27733    assert_eq!(sticky_headers(8.5), vec![]);
27734    assert_eq!(sticky_headers(9.0), vec![]);
27735    assert_eq!(sticky_headers(9.5), vec![]);
27736    assert_eq!(sticky_headers(10.0), vec![]);
27737}
27738
27739#[gpui::test]
27740async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
27741    init_test(cx, |_| {});
27742    cx.update(|cx| {
27743        SettingsStore::update_global(cx, |store, cx| {
27744            store.update_user_settings(cx, |settings| {
27745                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
27746                    enabled: Some(true),
27747                })
27748            });
27749        });
27750    });
27751    let mut cx = EditorTestContext::new(cx).await;
27752
27753    let line_height = cx.editor(|editor, window, _cx| {
27754        editor
27755            .style()
27756            .unwrap()
27757            .text
27758            .line_height_in_pixels(window.rem_size())
27759    });
27760
27761    let buffer = indoc! {"
27762            ˇfn foo() {
27763                let abc = 123;
27764            }
27765            struct Bar;
27766            impl Bar {
27767                fn new() -> Self {
27768                    Self
27769                }
27770            }
27771            fn baz() {
27772            }
27773        "};
27774    cx.set_state(&buffer);
27775
27776    cx.update_editor(|e, _, cx| {
27777        e.buffer()
27778            .read(cx)
27779            .as_singleton()
27780            .unwrap()
27781            .update(cx, |buffer, cx| {
27782                buffer.set_language(Some(rust_lang()), cx);
27783            })
27784    });
27785
27786    let fn_foo = || empty_range(0, 0);
27787    let impl_bar = || empty_range(4, 0);
27788    let fn_new = || empty_range(5, 4);
27789
27790    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
27791        cx.update_editor(|e, window, cx| {
27792            e.scroll(
27793                gpui::Point {
27794                    x: 0.,
27795                    y: scroll_offset,
27796                },
27797                None,
27798                window,
27799                cx,
27800            );
27801        });
27802        cx.simulate_click(
27803            gpui::Point {
27804                x: px(0.),
27805                y: click_offset as f32 * line_height,
27806            },
27807            Modifiers::none(),
27808        );
27809        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
27810    };
27811
27812    assert_eq!(
27813        scroll_and_click(
27814            4.5, // impl Bar is halfway off the screen
27815            0.0  // click top of screen
27816        ),
27817        // scrolled to impl Bar
27818        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27819    );
27820
27821    assert_eq!(
27822        scroll_and_click(
27823            4.5,  // impl Bar is halfway off the screen
27824            0.25  // click middle of impl Bar
27825        ),
27826        // scrolled to impl Bar
27827        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27828    );
27829
27830    assert_eq!(
27831        scroll_and_click(
27832            4.5, // impl Bar is halfway off the screen
27833            1.5  // click below impl Bar (e.g. fn new())
27834        ),
27835        // scrolled to fn new() - this is below the impl Bar header which has persisted
27836        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27837    );
27838
27839    assert_eq!(
27840        scroll_and_click(
27841            5.5,  // fn new is halfway underneath impl Bar
27842            0.75  // click on the overlap of impl Bar and fn new()
27843        ),
27844        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27845    );
27846
27847    assert_eq!(
27848        scroll_and_click(
27849            5.5,  // fn new is halfway underneath impl Bar
27850            1.25  // click on the visible part of fn new()
27851        ),
27852        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27853    );
27854
27855    assert_eq!(
27856        scroll_and_click(
27857            1.5, // fn foo is halfway off the screen
27858            0.0  // click top of screen
27859        ),
27860        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
27861    );
27862
27863    assert_eq!(
27864        scroll_and_click(
27865            1.5,  // fn foo is halfway off the screen
27866            0.75  // click visible part of let abc...
27867        )
27868        .0,
27869        // no change in scroll
27870        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
27871        (gpui::Point { x: 0., y: 1.5 })
27872    );
27873}
27874
27875#[gpui::test]
27876async fn test_next_prev_reference(cx: &mut TestAppContext) {
27877    const CYCLE_POSITIONS: &[&'static str] = &[
27878        indoc! {"
27879            fn foo() {
27880                let ˇabc = 123;
27881                let x = abc + 1;
27882                let y = abc + 2;
27883                let z = abc + 2;
27884            }
27885        "},
27886        indoc! {"
27887            fn foo() {
27888                let abc = 123;
27889                let x = ˇabc + 1;
27890                let y = abc + 2;
27891                let z = abc + 2;
27892            }
27893        "},
27894        indoc! {"
27895            fn foo() {
27896                let abc = 123;
27897                let x = abc + 1;
27898                let y = ˇabc + 2;
27899                let z = abc + 2;
27900            }
27901        "},
27902        indoc! {"
27903            fn foo() {
27904                let abc = 123;
27905                let x = abc + 1;
27906                let y = abc + 2;
27907                let z = ˇabc + 2;
27908            }
27909        "},
27910    ];
27911
27912    init_test(cx, |_| {});
27913
27914    let mut cx = EditorLspTestContext::new_rust(
27915        lsp::ServerCapabilities {
27916            references_provider: Some(lsp::OneOf::Left(true)),
27917            ..Default::default()
27918        },
27919        cx,
27920    )
27921    .await;
27922
27923    // importantly, the cursor is in the middle
27924    cx.set_state(indoc! {"
27925        fn foo() {
27926            let aˇbc = 123;
27927            let x = abc + 1;
27928            let y = abc + 2;
27929            let z = abc + 2;
27930        }
27931    "});
27932
27933    let reference_ranges = [
27934        lsp::Position::new(1, 8),
27935        lsp::Position::new(2, 12),
27936        lsp::Position::new(3, 12),
27937        lsp::Position::new(4, 12),
27938    ]
27939    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
27940
27941    cx.lsp
27942        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
27943            Ok(Some(
27944                reference_ranges
27945                    .map(|range| lsp::Location {
27946                        uri: params.text_document_position.text_document.uri.clone(),
27947                        range,
27948                    })
27949                    .to_vec(),
27950            ))
27951        });
27952
27953    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
27954        cx.update_editor(|editor, window, cx| {
27955            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
27956        })
27957        .unwrap()
27958        .await
27959        .unwrap()
27960    };
27961
27962    _move(Direction::Next, 1, &mut cx).await;
27963    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27964
27965    _move(Direction::Next, 1, &mut cx).await;
27966    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27967
27968    _move(Direction::Next, 1, &mut cx).await;
27969    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27970
27971    // loops back to the start
27972    _move(Direction::Next, 1, &mut cx).await;
27973    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27974
27975    // loops back to the end
27976    _move(Direction::Prev, 1, &mut cx).await;
27977    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27978
27979    _move(Direction::Prev, 1, &mut cx).await;
27980    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27981
27982    _move(Direction::Prev, 1, &mut cx).await;
27983    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27984
27985    _move(Direction::Prev, 1, &mut cx).await;
27986    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27987
27988    _move(Direction::Next, 3, &mut cx).await;
27989    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27990
27991    _move(Direction::Prev, 2, &mut cx).await;
27992    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27993}
27994
27995#[gpui::test]
27996async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
27997    init_test(cx, |_| {});
27998
27999    let (editor, cx) = cx.add_window_view(|window, cx| {
28000        let multi_buffer = MultiBuffer::build_multi(
28001            [
28002                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28003                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28004            ],
28005            cx,
28006        );
28007        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28008    });
28009
28010    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28011    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
28012
28013    cx.assert_excerpts_with_selections(indoc! {"
28014        [EXCERPT]
28015        ˇ1
28016        2
28017        3
28018        [EXCERPT]
28019        1
28020        2
28021        3
28022        "});
28023
28024    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
28025    cx.update_editor(|editor, window, cx| {
28026        editor.change_selections(None.into(), window, cx, |s| {
28027            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28028        });
28029    });
28030    cx.assert_excerpts_with_selections(indoc! {"
28031        [EXCERPT]
28032        1
2803328034        3
28035        [EXCERPT]
28036        1
28037        2
28038        3
28039        "});
28040
28041    cx.update_editor(|editor, window, cx| {
28042        editor
28043            .select_all_matches(&SelectAllMatches, window, cx)
28044            .unwrap();
28045    });
28046    cx.assert_excerpts_with_selections(indoc! {"
28047        [EXCERPT]
28048        1
2804928050        3
28051        [EXCERPT]
28052        1
2805328054        3
28055        "});
28056
28057    cx.update_editor(|editor, window, cx| {
28058        editor.handle_input("X", window, cx);
28059    });
28060    cx.assert_excerpts_with_selections(indoc! {"
28061        [EXCERPT]
28062        1
2806328064        3
28065        [EXCERPT]
28066        1
2806728068        3
28069        "});
28070
28071    // Scenario 2: Select "2", then fold second buffer before insertion
28072    cx.update_multibuffer(|mb, cx| {
28073        for buffer_id in buffer_ids.iter() {
28074            let buffer = mb.buffer(*buffer_id).unwrap();
28075            buffer.update(cx, |buffer, cx| {
28076                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28077            });
28078        }
28079    });
28080
28081    // Select "2" and select all matches
28082    cx.update_editor(|editor, window, cx| {
28083        editor.change_selections(None.into(), window, cx, |s| {
28084            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28085        });
28086        editor
28087            .select_all_matches(&SelectAllMatches, window, cx)
28088            .unwrap();
28089    });
28090
28091    // Fold second buffer - should remove selections from folded buffer
28092    cx.update_editor(|editor, _, cx| {
28093        editor.fold_buffer(buffer_ids[1], cx);
28094    });
28095    cx.assert_excerpts_with_selections(indoc! {"
28096        [EXCERPT]
28097        1
2809828099        3
28100        [EXCERPT]
28101        [FOLDED]
28102        "});
28103
28104    // Insert text - should only affect first buffer
28105    cx.update_editor(|editor, window, cx| {
28106        editor.handle_input("Y", window, cx);
28107    });
28108    cx.update_editor(|editor, _, cx| {
28109        editor.unfold_buffer(buffer_ids[1], cx);
28110    });
28111    cx.assert_excerpts_with_selections(indoc! {"
28112        [EXCERPT]
28113        1
2811428115        3
28116        [EXCERPT]
28117        1
28118        2
28119        3
28120        "});
28121
28122    // Scenario 3: Select "2", then fold first buffer before insertion
28123    cx.update_multibuffer(|mb, cx| {
28124        for buffer_id in buffer_ids.iter() {
28125            let buffer = mb.buffer(*buffer_id).unwrap();
28126            buffer.update(cx, |buffer, cx| {
28127                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28128            });
28129        }
28130    });
28131
28132    // Select "2" and select all matches
28133    cx.update_editor(|editor, window, cx| {
28134        editor.change_selections(None.into(), window, cx, |s| {
28135            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28136        });
28137        editor
28138            .select_all_matches(&SelectAllMatches, window, cx)
28139            .unwrap();
28140    });
28141
28142    // Fold first buffer - should remove selections from folded buffer
28143    cx.update_editor(|editor, _, cx| {
28144        editor.fold_buffer(buffer_ids[0], cx);
28145    });
28146    cx.assert_excerpts_with_selections(indoc! {"
28147        [EXCERPT]
28148        [FOLDED]
28149        [EXCERPT]
28150        1
2815128152        3
28153        "});
28154
28155    // Insert text - should only affect second buffer
28156    cx.update_editor(|editor, window, cx| {
28157        editor.handle_input("Z", window, cx);
28158    });
28159    cx.update_editor(|editor, _, cx| {
28160        editor.unfold_buffer(buffer_ids[0], cx);
28161    });
28162    cx.assert_excerpts_with_selections(indoc! {"
28163        [EXCERPT]
28164        1
28165        2
28166        3
28167        [EXCERPT]
28168        1
2816928170        3
28171        "});
28172
28173    // Edge case scenario: fold all buffers, then try to insert
28174    cx.update_editor(|editor, _, cx| {
28175        editor.fold_buffer(buffer_ids[0], cx);
28176        editor.fold_buffer(buffer_ids[1], cx);
28177    });
28178    cx.assert_excerpts_with_selections(indoc! {"
28179        [EXCERPT]
28180        ˇ[FOLDED]
28181        [EXCERPT]
28182        [FOLDED]
28183        "});
28184
28185    // Insert should work via default selection
28186    cx.update_editor(|editor, window, cx| {
28187        editor.handle_input("W", window, cx);
28188    });
28189    cx.update_editor(|editor, _, cx| {
28190        editor.unfold_buffer(buffer_ids[0], cx);
28191        editor.unfold_buffer(buffer_ids[1], cx);
28192    });
28193    cx.assert_excerpts_with_selections(indoc! {"
28194        [EXCERPT]
28195        Wˇ1
28196        2
28197        3
28198        [EXCERPT]
28199        1
28200        Z
28201        3
28202        "});
28203}