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        hunks
21554            .into_iter()
21555            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21556            .collect::<Vec<_>>()
21557    });
21558    assert_eq!(hunk_ranges.len(), 2);
21559
21560    cx.update_editor(|editor, _, cx| {
21561        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21562    });
21563    executor.run_until_parked();
21564
21565    let second_hunk_expanded = r#"
21566          ˇA
21567          b
21568        - c
21569        + C
21570        "#
21571    .unindent();
21572
21573    cx.assert_state_with_diff(second_hunk_expanded);
21574
21575    cx.update_editor(|editor, _, cx| {
21576        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21577    });
21578    executor.run_until_parked();
21579
21580    cx.assert_state_with_diff(both_hunks_expanded.clone());
21581
21582    cx.update_editor(|editor, _, cx| {
21583        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21584    });
21585    executor.run_until_parked();
21586
21587    let first_hunk_expanded = r#"
21588        - a
21589        + ˇA
21590          b
21591          C
21592        "#
21593    .unindent();
21594
21595    cx.assert_state_with_diff(first_hunk_expanded);
21596
21597    cx.update_editor(|editor, _, cx| {
21598        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21599    });
21600    executor.run_until_parked();
21601
21602    cx.assert_state_with_diff(both_hunks_expanded);
21603
21604    cx.set_state(
21605        &r#"
21606        ˇA
21607        b
21608        "#
21609        .unindent(),
21610    );
21611    cx.run_until_parked();
21612
21613    // TODO this cursor position seems bad
21614    cx.assert_state_with_diff(
21615        r#"
21616        - ˇa
21617        + A
21618          b
21619        "#
21620        .unindent(),
21621    );
21622
21623    cx.update_editor(|editor, window, cx| {
21624        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21625    });
21626
21627    cx.assert_state_with_diff(
21628        r#"
21629            - ˇa
21630            + A
21631              b
21632            - c
21633            "#
21634        .unindent(),
21635    );
21636
21637    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21638        let snapshot = editor.snapshot(window, cx);
21639        let hunks = editor
21640            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21641            .collect::<Vec<_>>();
21642        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21643        hunks
21644            .into_iter()
21645            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21646            .collect::<Vec<_>>()
21647    });
21648    assert_eq!(hunk_ranges.len(), 2);
21649
21650    cx.update_editor(|editor, _, cx| {
21651        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21652    });
21653    executor.run_until_parked();
21654
21655    cx.assert_state_with_diff(
21656        r#"
21657        - ˇa
21658        + A
21659          b
21660        "#
21661        .unindent(),
21662    );
21663}
21664
21665#[gpui::test]
21666async fn test_toggle_deletion_hunk_at_start_of_file(
21667    executor: BackgroundExecutor,
21668    cx: &mut TestAppContext,
21669) {
21670    init_test(cx, |_| {});
21671    let mut cx = EditorTestContext::new(cx).await;
21672
21673    let diff_base = r#"
21674        a
21675        b
21676        c
21677        "#
21678    .unindent();
21679
21680    cx.set_state(
21681        &r#"
21682        ˇb
21683        c
21684        "#
21685        .unindent(),
21686    );
21687    cx.set_head_text(&diff_base);
21688    cx.update_editor(|editor, window, cx| {
21689        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21690    });
21691    executor.run_until_parked();
21692
21693    let hunk_expanded = r#"
21694        - a
21695          ˇb
21696          c
21697        "#
21698    .unindent();
21699
21700    cx.assert_state_with_diff(hunk_expanded.clone());
21701
21702    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21703        let snapshot = editor.snapshot(window, cx);
21704        let hunks = editor
21705            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21706            .collect::<Vec<_>>();
21707        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21708        hunks
21709            .into_iter()
21710            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21711            .collect::<Vec<_>>()
21712    });
21713    assert_eq!(hunk_ranges.len(), 1);
21714
21715    cx.update_editor(|editor, _, cx| {
21716        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21717    });
21718    executor.run_until_parked();
21719
21720    let hunk_collapsed = r#"
21721          ˇb
21722          c
21723        "#
21724    .unindent();
21725
21726    cx.assert_state_with_diff(hunk_collapsed);
21727
21728    cx.update_editor(|editor, _, cx| {
21729        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21730    });
21731    executor.run_until_parked();
21732
21733    cx.assert_state_with_diff(hunk_expanded);
21734}
21735
21736#[gpui::test]
21737async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21738    init_test(cx, |_| {});
21739
21740    let fs = FakeFs::new(cx.executor());
21741    fs.insert_tree(
21742        path!("/test"),
21743        json!({
21744            ".git": {},
21745            "file-1": "ONE\n",
21746            "file-2": "TWO\n",
21747            "file-3": "THREE\n",
21748        }),
21749    )
21750    .await;
21751
21752    fs.set_head_for_repo(
21753        path!("/test/.git").as_ref(),
21754        &[
21755            ("file-1", "one\n".into()),
21756            ("file-2", "two\n".into()),
21757            ("file-3", "three\n".into()),
21758        ],
21759        "deadbeef",
21760    );
21761
21762    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21763    let mut buffers = vec![];
21764    for i in 1..=3 {
21765        let buffer = project
21766            .update(cx, |project, cx| {
21767                let path = format!(path!("/test/file-{}"), i);
21768                project.open_local_buffer(path, cx)
21769            })
21770            .await
21771            .unwrap();
21772        buffers.push(buffer);
21773    }
21774
21775    let multibuffer = cx.new(|cx| {
21776        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21777        multibuffer.set_all_diff_hunks_expanded(cx);
21778        for buffer in &buffers {
21779            let snapshot = buffer.read(cx).snapshot();
21780            multibuffer.set_excerpts_for_path(
21781                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21782                buffer.clone(),
21783                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21784                2,
21785                cx,
21786            );
21787        }
21788        multibuffer
21789    });
21790
21791    let editor = cx.add_window(|window, cx| {
21792        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21793    });
21794    cx.run_until_parked();
21795
21796    let snapshot = editor
21797        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21798        .unwrap();
21799    let hunks = snapshot
21800        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21801        .map(|hunk| match hunk {
21802            DisplayDiffHunk::Unfolded {
21803                display_row_range, ..
21804            } => display_row_range,
21805            DisplayDiffHunk::Folded { .. } => unreachable!(),
21806        })
21807        .collect::<Vec<_>>();
21808    assert_eq!(
21809        hunks,
21810        [
21811            DisplayRow(2)..DisplayRow(4),
21812            DisplayRow(7)..DisplayRow(9),
21813            DisplayRow(12)..DisplayRow(14),
21814        ]
21815    );
21816}
21817
21818#[gpui::test]
21819async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21820    init_test(cx, |_| {});
21821
21822    let mut cx = EditorTestContext::new(cx).await;
21823    cx.set_head_text(indoc! { "
21824        one
21825        two
21826        three
21827        four
21828        five
21829        "
21830    });
21831    cx.set_index_text(indoc! { "
21832        one
21833        two
21834        three
21835        four
21836        five
21837        "
21838    });
21839    cx.set_state(indoc! {"
21840        one
21841        TWO
21842        ˇTHREE
21843        FOUR
21844        five
21845    "});
21846    cx.run_until_parked();
21847    cx.update_editor(|editor, window, cx| {
21848        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21849    });
21850    cx.run_until_parked();
21851    cx.assert_index_text(Some(indoc! {"
21852        one
21853        TWO
21854        THREE
21855        FOUR
21856        five
21857    "}));
21858    cx.set_state(indoc! { "
21859        one
21860        TWO
21861        ˇTHREE-HUNDRED
21862        FOUR
21863        five
21864    "});
21865    cx.run_until_parked();
21866    cx.update_editor(|editor, window, cx| {
21867        let snapshot = editor.snapshot(window, cx);
21868        let hunks = editor
21869            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21870            .collect::<Vec<_>>();
21871        assert_eq!(hunks.len(), 1);
21872        assert_eq!(
21873            hunks[0].status(),
21874            DiffHunkStatus {
21875                kind: DiffHunkStatusKind::Modified,
21876                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21877            }
21878        );
21879
21880        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21881    });
21882    cx.run_until_parked();
21883    cx.assert_index_text(Some(indoc! {"
21884        one
21885        TWO
21886        THREE-HUNDRED
21887        FOUR
21888        five
21889    "}));
21890}
21891
21892#[gpui::test]
21893fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21894    init_test(cx, |_| {});
21895
21896    let editor = cx.add_window(|window, cx| {
21897        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21898        build_editor(buffer, window, cx)
21899    });
21900
21901    let render_args = Arc::new(Mutex::new(None));
21902    let snapshot = editor
21903        .update(cx, |editor, window, cx| {
21904            let snapshot = editor.buffer().read(cx).snapshot(cx);
21905            let range =
21906                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21907
21908            struct RenderArgs {
21909                row: MultiBufferRow,
21910                folded: bool,
21911                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21912            }
21913
21914            let crease = Crease::inline(
21915                range,
21916                FoldPlaceholder::test(),
21917                {
21918                    let toggle_callback = render_args.clone();
21919                    move |row, folded, callback, _window, _cx| {
21920                        *toggle_callback.lock() = Some(RenderArgs {
21921                            row,
21922                            folded,
21923                            callback,
21924                        });
21925                        div()
21926                    }
21927                },
21928                |_row, _folded, _window, _cx| div(),
21929            );
21930
21931            editor.insert_creases(Some(crease), cx);
21932            let snapshot = editor.snapshot(window, cx);
21933            let _div =
21934                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21935            snapshot
21936        })
21937        .unwrap();
21938
21939    let render_args = render_args.lock().take().unwrap();
21940    assert_eq!(render_args.row, MultiBufferRow(1));
21941    assert!(!render_args.folded);
21942    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21943
21944    cx.update_window(*editor, |_, window, cx| {
21945        (render_args.callback)(true, window, cx)
21946    })
21947    .unwrap();
21948    let snapshot = editor
21949        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21950        .unwrap();
21951    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21952
21953    cx.update_window(*editor, |_, window, cx| {
21954        (render_args.callback)(false, window, cx)
21955    })
21956    .unwrap();
21957    let snapshot = editor
21958        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21959        .unwrap();
21960    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21961}
21962
21963#[gpui::test]
21964async fn test_input_text(cx: &mut TestAppContext) {
21965    init_test(cx, |_| {});
21966    let mut cx = EditorTestContext::new(cx).await;
21967
21968    cx.set_state(
21969        &r#"ˇone
21970        two
21971
21972        three
21973        fourˇ
21974        five
21975
21976        siˇx"#
21977            .unindent(),
21978    );
21979
21980    cx.dispatch_action(HandleInput(String::new()));
21981    cx.assert_editor_state(
21982        &r#"ˇone
21983        two
21984
21985        three
21986        fourˇ
21987        five
21988
21989        siˇx"#
21990            .unindent(),
21991    );
21992
21993    cx.dispatch_action(HandleInput("AAAA".to_string()));
21994    cx.assert_editor_state(
21995        &r#"AAAAˇone
21996        two
21997
21998        three
21999        fourAAAAˇ
22000        five
22001
22002        siAAAAˇx"#
22003            .unindent(),
22004    );
22005}
22006
22007#[gpui::test]
22008async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22009    init_test(cx, |_| {});
22010
22011    let mut cx = EditorTestContext::new(cx).await;
22012    cx.set_state(
22013        r#"let foo = 1;
22014let foo = 2;
22015let foo = 3;
22016let fooˇ = 4;
22017let foo = 5;
22018let foo = 6;
22019let foo = 7;
22020let foo = 8;
22021let foo = 9;
22022let foo = 10;
22023let foo = 11;
22024let foo = 12;
22025let foo = 13;
22026let foo = 14;
22027let foo = 15;"#,
22028    );
22029
22030    cx.update_editor(|e, window, cx| {
22031        assert_eq!(
22032            e.next_scroll_position,
22033            NextScrollCursorCenterTopBottom::Center,
22034            "Default next scroll direction is center",
22035        );
22036
22037        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22038        assert_eq!(
22039            e.next_scroll_position,
22040            NextScrollCursorCenterTopBottom::Top,
22041            "After center, next scroll direction should be top",
22042        );
22043
22044        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22045        assert_eq!(
22046            e.next_scroll_position,
22047            NextScrollCursorCenterTopBottom::Bottom,
22048            "After top, next scroll direction should be bottom",
22049        );
22050
22051        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22052        assert_eq!(
22053            e.next_scroll_position,
22054            NextScrollCursorCenterTopBottom::Center,
22055            "After bottom, scrolling should start over",
22056        );
22057
22058        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22059        assert_eq!(
22060            e.next_scroll_position,
22061            NextScrollCursorCenterTopBottom::Top,
22062            "Scrolling continues if retriggered fast enough"
22063        );
22064    });
22065
22066    cx.executor()
22067        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22068    cx.executor().run_until_parked();
22069    cx.update_editor(|e, _, _| {
22070        assert_eq!(
22071            e.next_scroll_position,
22072            NextScrollCursorCenterTopBottom::Center,
22073            "If scrolling is not triggered fast enough, it should reset"
22074        );
22075    });
22076}
22077
22078#[gpui::test]
22079async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22080    init_test(cx, |_| {});
22081    let mut cx = EditorLspTestContext::new_rust(
22082        lsp::ServerCapabilities {
22083            definition_provider: Some(lsp::OneOf::Left(true)),
22084            references_provider: Some(lsp::OneOf::Left(true)),
22085            ..lsp::ServerCapabilities::default()
22086        },
22087        cx,
22088    )
22089    .await;
22090
22091    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22092        let go_to_definition = cx
22093            .lsp
22094            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22095                move |params, _| async move {
22096                    if empty_go_to_definition {
22097                        Ok(None)
22098                    } else {
22099                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22100                            uri: params.text_document_position_params.text_document.uri,
22101                            range: lsp::Range::new(
22102                                lsp::Position::new(4, 3),
22103                                lsp::Position::new(4, 6),
22104                            ),
22105                        })))
22106                    }
22107                },
22108            );
22109        let references = cx
22110            .lsp
22111            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22112                Ok(Some(vec![lsp::Location {
22113                    uri: params.text_document_position.text_document.uri,
22114                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22115                }]))
22116            });
22117        (go_to_definition, references)
22118    };
22119
22120    cx.set_state(
22121        &r#"fn one() {
22122            let mut a = ˇtwo();
22123        }
22124
22125        fn two() {}"#
22126            .unindent(),
22127    );
22128    set_up_lsp_handlers(false, &mut cx);
22129    let navigated = cx
22130        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22131        .await
22132        .expect("Failed to navigate to definition");
22133    assert_eq!(
22134        navigated,
22135        Navigated::Yes,
22136        "Should have navigated to definition from the GetDefinition response"
22137    );
22138    cx.assert_editor_state(
22139        &r#"fn one() {
22140            let mut a = two();
22141        }
22142
22143        fn «twoˇ»() {}"#
22144            .unindent(),
22145    );
22146
22147    let editors = cx.update_workspace(|workspace, _, cx| {
22148        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22149    });
22150    cx.update_editor(|_, _, test_editor_cx| {
22151        assert_eq!(
22152            editors.len(),
22153            1,
22154            "Initially, only one, test, editor should be open in the workspace"
22155        );
22156        assert_eq!(
22157            test_editor_cx.entity(),
22158            editors.last().expect("Asserted len is 1").clone()
22159        );
22160    });
22161
22162    set_up_lsp_handlers(true, &mut cx);
22163    let navigated = cx
22164        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22165        .await
22166        .expect("Failed to navigate to lookup references");
22167    assert_eq!(
22168        navigated,
22169        Navigated::Yes,
22170        "Should have navigated to references as a fallback after empty GoToDefinition response"
22171    );
22172    // We should not change the selections in the existing file,
22173    // if opening another milti buffer with the references
22174    cx.assert_editor_state(
22175        &r#"fn one() {
22176            let mut a = two();
22177        }
22178
22179        fn «twoˇ»() {}"#
22180            .unindent(),
22181    );
22182    let editors = cx.update_workspace(|workspace, _, cx| {
22183        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22184    });
22185    cx.update_editor(|_, _, test_editor_cx| {
22186        assert_eq!(
22187            editors.len(),
22188            2,
22189            "After falling back to references search, we open a new editor with the results"
22190        );
22191        let references_fallback_text = editors
22192            .into_iter()
22193            .find(|new_editor| *new_editor != test_editor_cx.entity())
22194            .expect("Should have one non-test editor now")
22195            .read(test_editor_cx)
22196            .text(test_editor_cx);
22197        assert_eq!(
22198            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22199            "Should use the range from the references response and not the GoToDefinition one"
22200        );
22201    });
22202}
22203
22204#[gpui::test]
22205async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22206    init_test(cx, |_| {});
22207    cx.update(|cx| {
22208        let mut editor_settings = EditorSettings::get_global(cx).clone();
22209        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22210        EditorSettings::override_global(editor_settings, cx);
22211    });
22212    let mut cx = EditorLspTestContext::new_rust(
22213        lsp::ServerCapabilities {
22214            definition_provider: Some(lsp::OneOf::Left(true)),
22215            references_provider: Some(lsp::OneOf::Left(true)),
22216            ..lsp::ServerCapabilities::default()
22217        },
22218        cx,
22219    )
22220    .await;
22221    let original_state = r#"fn one() {
22222        let mut a = ˇtwo();
22223    }
22224
22225    fn two() {}"#
22226        .unindent();
22227    cx.set_state(&original_state);
22228
22229    let mut go_to_definition = cx
22230        .lsp
22231        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22232            move |_, _| async move { Ok(None) },
22233        );
22234    let _references = cx
22235        .lsp
22236        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22237            panic!("Should not call for references with no go to definition fallback")
22238        });
22239
22240    let navigated = cx
22241        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22242        .await
22243        .expect("Failed to navigate to lookup references");
22244    go_to_definition
22245        .next()
22246        .await
22247        .expect("Should have called the go_to_definition handler");
22248
22249    assert_eq!(
22250        navigated,
22251        Navigated::No,
22252        "Should have navigated to references as a fallback after empty GoToDefinition response"
22253    );
22254    cx.assert_editor_state(&original_state);
22255    let editors = cx.update_workspace(|workspace, _, cx| {
22256        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22257    });
22258    cx.update_editor(|_, _, _| {
22259        assert_eq!(
22260            editors.len(),
22261            1,
22262            "After unsuccessful fallback, no other editor should have been opened"
22263        );
22264    });
22265}
22266
22267#[gpui::test]
22268async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22269    init_test(cx, |_| {});
22270    let mut cx = EditorLspTestContext::new_rust(
22271        lsp::ServerCapabilities {
22272            references_provider: Some(lsp::OneOf::Left(true)),
22273            ..lsp::ServerCapabilities::default()
22274        },
22275        cx,
22276    )
22277    .await;
22278
22279    cx.set_state(
22280        &r#"
22281        fn one() {
22282            let mut a = two();
22283        }
22284
22285        fn ˇtwo() {}"#
22286            .unindent(),
22287    );
22288    cx.lsp
22289        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22290            Ok(Some(vec![
22291                lsp::Location {
22292                    uri: params.text_document_position.text_document.uri.clone(),
22293                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22294                },
22295                lsp::Location {
22296                    uri: params.text_document_position.text_document.uri,
22297                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22298                },
22299            ]))
22300        });
22301    let navigated = cx
22302        .update_editor(|editor, window, cx| {
22303            editor.find_all_references(&FindAllReferences, window, cx)
22304        })
22305        .unwrap()
22306        .await
22307        .expect("Failed to navigate to references");
22308    assert_eq!(
22309        navigated,
22310        Navigated::Yes,
22311        "Should have navigated to references from the FindAllReferences response"
22312    );
22313    cx.assert_editor_state(
22314        &r#"fn one() {
22315            let mut a = two();
22316        }
22317
22318        fn ˇtwo() {}"#
22319            .unindent(),
22320    );
22321
22322    let editors = cx.update_workspace(|workspace, _, cx| {
22323        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22324    });
22325    cx.update_editor(|_, _, _| {
22326        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22327    });
22328
22329    cx.set_state(
22330        &r#"fn one() {
22331            let mut a = ˇtwo();
22332        }
22333
22334        fn two() {}"#
22335            .unindent(),
22336    );
22337    let navigated = cx
22338        .update_editor(|editor, window, cx| {
22339            editor.find_all_references(&FindAllReferences, window, cx)
22340        })
22341        .unwrap()
22342        .await
22343        .expect("Failed to navigate to references");
22344    assert_eq!(
22345        navigated,
22346        Navigated::Yes,
22347        "Should have navigated to references from the FindAllReferences response"
22348    );
22349    cx.assert_editor_state(
22350        &r#"fn one() {
22351            let mut a = ˇtwo();
22352        }
22353
22354        fn two() {}"#
22355            .unindent(),
22356    );
22357    let editors = cx.update_workspace(|workspace, _, cx| {
22358        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22359    });
22360    cx.update_editor(|_, _, _| {
22361        assert_eq!(
22362            editors.len(),
22363            2,
22364            "should have re-used the previous multibuffer"
22365        );
22366    });
22367
22368    cx.set_state(
22369        &r#"fn one() {
22370            let mut a = ˇtwo();
22371        }
22372        fn three() {}
22373        fn two() {}"#
22374            .unindent(),
22375    );
22376    cx.lsp
22377        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22378            Ok(Some(vec![
22379                lsp::Location {
22380                    uri: params.text_document_position.text_document.uri.clone(),
22381                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22382                },
22383                lsp::Location {
22384                    uri: params.text_document_position.text_document.uri,
22385                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22386                },
22387            ]))
22388        });
22389    let navigated = cx
22390        .update_editor(|editor, window, cx| {
22391            editor.find_all_references(&FindAllReferences, window, cx)
22392        })
22393        .unwrap()
22394        .await
22395        .expect("Failed to navigate to references");
22396    assert_eq!(
22397        navigated,
22398        Navigated::Yes,
22399        "Should have navigated to references from the FindAllReferences response"
22400    );
22401    cx.assert_editor_state(
22402        &r#"fn one() {
22403                let mut a = ˇtwo();
22404            }
22405            fn three() {}
22406            fn two() {}"#
22407            .unindent(),
22408    );
22409    let editors = cx.update_workspace(|workspace, _, cx| {
22410        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22411    });
22412    cx.update_editor(|_, _, _| {
22413        assert_eq!(
22414            editors.len(),
22415            3,
22416            "should have used a new multibuffer as offsets changed"
22417        );
22418    });
22419}
22420#[gpui::test]
22421async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22422    init_test(cx, |_| {});
22423
22424    let language = Arc::new(Language::new(
22425        LanguageConfig::default(),
22426        Some(tree_sitter_rust::LANGUAGE.into()),
22427    ));
22428
22429    let text = r#"
22430        #[cfg(test)]
22431        mod tests() {
22432            #[test]
22433            fn runnable_1() {
22434                let a = 1;
22435            }
22436
22437            #[test]
22438            fn runnable_2() {
22439                let a = 1;
22440                let b = 2;
22441            }
22442        }
22443    "#
22444    .unindent();
22445
22446    let fs = FakeFs::new(cx.executor());
22447    fs.insert_file("/file.rs", Default::default()).await;
22448
22449    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22450    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22451    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22452    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22453    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22454
22455    let editor = cx.new_window_entity(|window, cx| {
22456        Editor::new(
22457            EditorMode::full(),
22458            multi_buffer,
22459            Some(project.clone()),
22460            window,
22461            cx,
22462        )
22463    });
22464
22465    editor.update_in(cx, |editor, window, cx| {
22466        let snapshot = editor.buffer().read(cx).snapshot(cx);
22467        editor.tasks.insert(
22468            (buffer.read(cx).remote_id(), 3),
22469            RunnableTasks {
22470                templates: vec![],
22471                offset: snapshot.anchor_before(MultiBufferOffset(43)),
22472                column: 0,
22473                extra_variables: HashMap::default(),
22474                context_range: BufferOffset(43)..BufferOffset(85),
22475            },
22476        );
22477        editor.tasks.insert(
22478            (buffer.read(cx).remote_id(), 8),
22479            RunnableTasks {
22480                templates: vec![],
22481                offset: snapshot.anchor_before(MultiBufferOffset(86)),
22482                column: 0,
22483                extra_variables: HashMap::default(),
22484                context_range: BufferOffset(86)..BufferOffset(191),
22485            },
22486        );
22487
22488        // Test finding task when cursor is inside function body
22489        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22490            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22491        });
22492        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22493        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22494
22495        // Test finding task when cursor is on function name
22496        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22497            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22498        });
22499        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22500        assert_eq!(row, 8, "Should find task when cursor is on function name");
22501    });
22502}
22503
22504#[gpui::test]
22505async fn test_folding_buffers(cx: &mut TestAppContext) {
22506    init_test(cx, |_| {});
22507
22508    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22509    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22510    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22511
22512    let fs = FakeFs::new(cx.executor());
22513    fs.insert_tree(
22514        path!("/a"),
22515        json!({
22516            "first.rs": sample_text_1,
22517            "second.rs": sample_text_2,
22518            "third.rs": sample_text_3,
22519        }),
22520    )
22521    .await;
22522    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22523    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22524    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22525    let worktree = project.update(cx, |project, cx| {
22526        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22527        assert_eq!(worktrees.len(), 1);
22528        worktrees.pop().unwrap()
22529    });
22530    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22531
22532    let buffer_1 = project
22533        .update(cx, |project, cx| {
22534            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22535        })
22536        .await
22537        .unwrap();
22538    let buffer_2 = project
22539        .update(cx, |project, cx| {
22540            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22541        })
22542        .await
22543        .unwrap();
22544    let buffer_3 = project
22545        .update(cx, |project, cx| {
22546            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22547        })
22548        .await
22549        .unwrap();
22550
22551    let multi_buffer = cx.new(|cx| {
22552        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22553        multi_buffer.push_excerpts(
22554            buffer_1.clone(),
22555            [
22556                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22557                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22558                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22559            ],
22560            cx,
22561        );
22562        multi_buffer.push_excerpts(
22563            buffer_2.clone(),
22564            [
22565                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22566                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22567                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22568            ],
22569            cx,
22570        );
22571        multi_buffer.push_excerpts(
22572            buffer_3.clone(),
22573            [
22574                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22575                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22576                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22577            ],
22578            cx,
22579        );
22580        multi_buffer
22581    });
22582    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22583        Editor::new(
22584            EditorMode::full(),
22585            multi_buffer.clone(),
22586            Some(project.clone()),
22587            window,
22588            cx,
22589        )
22590    });
22591
22592    assert_eq!(
22593        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22594        "\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",
22595    );
22596
22597    multi_buffer_editor.update(cx, |editor, cx| {
22598        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22599    });
22600    assert_eq!(
22601        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22602        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22603        "After folding the first buffer, its text should not be displayed"
22604    );
22605
22606    multi_buffer_editor.update(cx, |editor, cx| {
22607        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22608    });
22609    assert_eq!(
22610        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22611        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22612        "After folding the second buffer, its text should not be displayed"
22613    );
22614
22615    multi_buffer_editor.update(cx, |editor, cx| {
22616        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22617    });
22618    assert_eq!(
22619        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22620        "\n\n\n\n\n",
22621        "After folding the third buffer, its text should not be displayed"
22622    );
22623
22624    // Emulate selection inside the fold logic, that should work
22625    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22626        editor
22627            .snapshot(window, cx)
22628            .next_line_boundary(Point::new(0, 4));
22629    });
22630
22631    multi_buffer_editor.update(cx, |editor, cx| {
22632        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22633    });
22634    assert_eq!(
22635        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22636        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22637        "After unfolding the second buffer, its text should be displayed"
22638    );
22639
22640    // Typing inside of buffer 1 causes that buffer to be unfolded.
22641    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22642        assert_eq!(
22643            multi_buffer
22644                .read(cx)
22645                .snapshot(cx)
22646                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22647                .collect::<String>(),
22648            "bbbb"
22649        );
22650        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22651            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22652        });
22653        editor.handle_input("B", window, cx);
22654    });
22655
22656    assert_eq!(
22657        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22658        "\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",
22659        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22660    );
22661
22662    multi_buffer_editor.update(cx, |editor, cx| {
22663        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22664    });
22665    assert_eq!(
22666        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22667        "\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",
22668        "After unfolding the all buffers, all original text should be displayed"
22669    );
22670}
22671
22672#[gpui::test]
22673async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22674    init_test(cx, |_| {});
22675
22676    let sample_text_1 = "1111\n2222\n3333".to_string();
22677    let sample_text_2 = "4444\n5555\n6666".to_string();
22678    let sample_text_3 = "7777\n8888\n9999".to_string();
22679
22680    let fs = FakeFs::new(cx.executor());
22681    fs.insert_tree(
22682        path!("/a"),
22683        json!({
22684            "first.rs": sample_text_1,
22685            "second.rs": sample_text_2,
22686            "third.rs": sample_text_3,
22687        }),
22688    )
22689    .await;
22690    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22691    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22692    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22693    let worktree = project.update(cx, |project, cx| {
22694        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22695        assert_eq!(worktrees.len(), 1);
22696        worktrees.pop().unwrap()
22697    });
22698    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22699
22700    let buffer_1 = project
22701        .update(cx, |project, cx| {
22702            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22703        })
22704        .await
22705        .unwrap();
22706    let buffer_2 = project
22707        .update(cx, |project, cx| {
22708            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22709        })
22710        .await
22711        .unwrap();
22712    let buffer_3 = project
22713        .update(cx, |project, cx| {
22714            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22715        })
22716        .await
22717        .unwrap();
22718
22719    let multi_buffer = cx.new(|cx| {
22720        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22721        multi_buffer.push_excerpts(
22722            buffer_1.clone(),
22723            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22724            cx,
22725        );
22726        multi_buffer.push_excerpts(
22727            buffer_2.clone(),
22728            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22729            cx,
22730        );
22731        multi_buffer.push_excerpts(
22732            buffer_3.clone(),
22733            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22734            cx,
22735        );
22736        multi_buffer
22737    });
22738
22739    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22740        Editor::new(
22741            EditorMode::full(),
22742            multi_buffer,
22743            Some(project.clone()),
22744            window,
22745            cx,
22746        )
22747    });
22748
22749    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22750    assert_eq!(
22751        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22752        full_text,
22753    );
22754
22755    multi_buffer_editor.update(cx, |editor, cx| {
22756        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22757    });
22758    assert_eq!(
22759        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22760        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22761        "After folding the first buffer, its text should not be displayed"
22762    );
22763
22764    multi_buffer_editor.update(cx, |editor, cx| {
22765        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22766    });
22767
22768    assert_eq!(
22769        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22770        "\n\n\n\n\n\n7777\n8888\n9999",
22771        "After folding the second buffer, its text should not be displayed"
22772    );
22773
22774    multi_buffer_editor.update(cx, |editor, cx| {
22775        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22776    });
22777    assert_eq!(
22778        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22779        "\n\n\n\n\n",
22780        "After folding the third buffer, its text should not be displayed"
22781    );
22782
22783    multi_buffer_editor.update(cx, |editor, cx| {
22784        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22785    });
22786    assert_eq!(
22787        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22788        "\n\n\n\n4444\n5555\n6666\n\n",
22789        "After unfolding the second buffer, its text should be displayed"
22790    );
22791
22792    multi_buffer_editor.update(cx, |editor, cx| {
22793        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22794    });
22795    assert_eq!(
22796        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22797        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22798        "After unfolding the first buffer, its text should be displayed"
22799    );
22800
22801    multi_buffer_editor.update(cx, |editor, cx| {
22802        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22803    });
22804    assert_eq!(
22805        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22806        full_text,
22807        "After unfolding all buffers, all original text should be displayed"
22808    );
22809}
22810
22811#[gpui::test]
22812async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22813    init_test(cx, |_| {});
22814
22815    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22816
22817    let fs = FakeFs::new(cx.executor());
22818    fs.insert_tree(
22819        path!("/a"),
22820        json!({
22821            "main.rs": sample_text,
22822        }),
22823    )
22824    .await;
22825    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22826    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22827    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22828    let worktree = project.update(cx, |project, cx| {
22829        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22830        assert_eq!(worktrees.len(), 1);
22831        worktrees.pop().unwrap()
22832    });
22833    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22834
22835    let buffer_1 = project
22836        .update(cx, |project, cx| {
22837            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22838        })
22839        .await
22840        .unwrap();
22841
22842    let multi_buffer = cx.new(|cx| {
22843        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22844        multi_buffer.push_excerpts(
22845            buffer_1.clone(),
22846            [ExcerptRange::new(
22847                Point::new(0, 0)
22848                    ..Point::new(
22849                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22850                        0,
22851                    ),
22852            )],
22853            cx,
22854        );
22855        multi_buffer
22856    });
22857    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22858        Editor::new(
22859            EditorMode::full(),
22860            multi_buffer,
22861            Some(project.clone()),
22862            window,
22863            cx,
22864        )
22865    });
22866
22867    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22868    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22869        enum TestHighlight {}
22870        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22871        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22872        editor.highlight_text::<TestHighlight>(
22873            vec![highlight_range.clone()],
22874            HighlightStyle::color(Hsla::green()),
22875            cx,
22876        );
22877        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22878            s.select_ranges(Some(highlight_range))
22879        });
22880    });
22881
22882    let full_text = format!("\n\n{sample_text}");
22883    assert_eq!(
22884        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22885        full_text,
22886    );
22887}
22888
22889#[gpui::test]
22890async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22891    init_test(cx, |_| {});
22892    cx.update(|cx| {
22893        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22894            "keymaps/default-linux.json",
22895            cx,
22896        )
22897        .unwrap();
22898        cx.bind_keys(default_key_bindings);
22899    });
22900
22901    let (editor, cx) = cx.add_window_view(|window, cx| {
22902        let multi_buffer = MultiBuffer::build_multi(
22903            [
22904                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22905                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22906                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22907                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22908            ],
22909            cx,
22910        );
22911        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22912
22913        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22914        // fold all but the second buffer, so that we test navigating between two
22915        // adjacent folded buffers, as well as folded buffers at the start and
22916        // end the multibuffer
22917        editor.fold_buffer(buffer_ids[0], cx);
22918        editor.fold_buffer(buffer_ids[2], cx);
22919        editor.fold_buffer(buffer_ids[3], cx);
22920
22921        editor
22922    });
22923    cx.simulate_resize(size(px(1000.), px(1000.)));
22924
22925    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22926    cx.assert_excerpts_with_selections(indoc! {"
22927        [EXCERPT]
22928        ˇ[FOLDED]
22929        [EXCERPT]
22930        a1
22931        b1
22932        [EXCERPT]
22933        [FOLDED]
22934        [EXCERPT]
22935        [FOLDED]
22936        "
22937    });
22938    cx.simulate_keystroke("down");
22939    cx.assert_excerpts_with_selections(indoc! {"
22940        [EXCERPT]
22941        [FOLDED]
22942        [EXCERPT]
22943        ˇa1
22944        b1
22945        [EXCERPT]
22946        [FOLDED]
22947        [EXCERPT]
22948        [FOLDED]
22949        "
22950    });
22951    cx.simulate_keystroke("down");
22952    cx.assert_excerpts_with_selections(indoc! {"
22953        [EXCERPT]
22954        [FOLDED]
22955        [EXCERPT]
22956        a1
22957        ˇb1
22958        [EXCERPT]
22959        [FOLDED]
22960        [EXCERPT]
22961        [FOLDED]
22962        "
22963    });
22964    cx.simulate_keystroke("down");
22965    cx.assert_excerpts_with_selections(indoc! {"
22966        [EXCERPT]
22967        [FOLDED]
22968        [EXCERPT]
22969        a1
22970        b1
22971        ˇ[EXCERPT]
22972        [FOLDED]
22973        [EXCERPT]
22974        [FOLDED]
22975        "
22976    });
22977    cx.simulate_keystroke("down");
22978    cx.assert_excerpts_with_selections(indoc! {"
22979        [EXCERPT]
22980        [FOLDED]
22981        [EXCERPT]
22982        a1
22983        b1
22984        [EXCERPT]
22985        ˇ[FOLDED]
22986        [EXCERPT]
22987        [FOLDED]
22988        "
22989    });
22990    for _ in 0..5 {
22991        cx.simulate_keystroke("down");
22992        cx.assert_excerpts_with_selections(indoc! {"
22993            [EXCERPT]
22994            [FOLDED]
22995            [EXCERPT]
22996            a1
22997            b1
22998            [EXCERPT]
22999            [FOLDED]
23000            [EXCERPT]
23001            ˇ[FOLDED]
23002            "
23003        });
23004    }
23005
23006    cx.simulate_keystroke("up");
23007    cx.assert_excerpts_with_selections(indoc! {"
23008        [EXCERPT]
23009        [FOLDED]
23010        [EXCERPT]
23011        a1
23012        b1
23013        [EXCERPT]
23014        ˇ[FOLDED]
23015        [EXCERPT]
23016        [FOLDED]
23017        "
23018    });
23019    cx.simulate_keystroke("up");
23020    cx.assert_excerpts_with_selections(indoc! {"
23021        [EXCERPT]
23022        [FOLDED]
23023        [EXCERPT]
23024        a1
23025        b1
23026        ˇ[EXCERPT]
23027        [FOLDED]
23028        [EXCERPT]
23029        [FOLDED]
23030        "
23031    });
23032    cx.simulate_keystroke("up");
23033    cx.assert_excerpts_with_selections(indoc! {"
23034        [EXCERPT]
23035        [FOLDED]
23036        [EXCERPT]
23037        a1
23038        ˇb1
23039        [EXCERPT]
23040        [FOLDED]
23041        [EXCERPT]
23042        [FOLDED]
23043        "
23044    });
23045    cx.simulate_keystroke("up");
23046    cx.assert_excerpts_with_selections(indoc! {"
23047        [EXCERPT]
23048        [FOLDED]
23049        [EXCERPT]
23050        ˇa1
23051        b1
23052        [EXCERPT]
23053        [FOLDED]
23054        [EXCERPT]
23055        [FOLDED]
23056        "
23057    });
23058    for _ in 0..5 {
23059        cx.simulate_keystroke("up");
23060        cx.assert_excerpts_with_selections(indoc! {"
23061            [EXCERPT]
23062            ˇ[FOLDED]
23063            [EXCERPT]
23064            a1
23065            b1
23066            [EXCERPT]
23067            [FOLDED]
23068            [EXCERPT]
23069            [FOLDED]
23070            "
23071        });
23072    }
23073}
23074
23075#[gpui::test]
23076async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23077    init_test(cx, |_| {});
23078
23079    // Simple insertion
23080    assert_highlighted_edits(
23081        "Hello, world!",
23082        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23083        true,
23084        cx,
23085        |highlighted_edits, cx| {
23086            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23087            assert_eq!(highlighted_edits.highlights.len(), 1);
23088            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23089            assert_eq!(
23090                highlighted_edits.highlights[0].1.background_color,
23091                Some(cx.theme().status().created_background)
23092            );
23093        },
23094    )
23095    .await;
23096
23097    // Replacement
23098    assert_highlighted_edits(
23099        "This is a test.",
23100        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23101        false,
23102        cx,
23103        |highlighted_edits, cx| {
23104            assert_eq!(highlighted_edits.text, "That is a test.");
23105            assert_eq!(highlighted_edits.highlights.len(), 1);
23106            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23107            assert_eq!(
23108                highlighted_edits.highlights[0].1.background_color,
23109                Some(cx.theme().status().created_background)
23110            );
23111        },
23112    )
23113    .await;
23114
23115    // Multiple edits
23116    assert_highlighted_edits(
23117        "Hello, world!",
23118        vec![
23119            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23120            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23121        ],
23122        false,
23123        cx,
23124        |highlighted_edits, cx| {
23125            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23126            assert_eq!(highlighted_edits.highlights.len(), 2);
23127            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23128            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23129            assert_eq!(
23130                highlighted_edits.highlights[0].1.background_color,
23131                Some(cx.theme().status().created_background)
23132            );
23133            assert_eq!(
23134                highlighted_edits.highlights[1].1.background_color,
23135                Some(cx.theme().status().created_background)
23136            );
23137        },
23138    )
23139    .await;
23140
23141    // Multiple lines with edits
23142    assert_highlighted_edits(
23143        "First line\nSecond line\nThird line\nFourth line",
23144        vec![
23145            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23146            (
23147                Point::new(2, 0)..Point::new(2, 10),
23148                "New third line".to_string(),
23149            ),
23150            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23151        ],
23152        false,
23153        cx,
23154        |highlighted_edits, cx| {
23155            assert_eq!(
23156                highlighted_edits.text,
23157                "Second modified\nNew third line\nFourth updated line"
23158            );
23159            assert_eq!(highlighted_edits.highlights.len(), 3);
23160            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23161            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23162            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23163            for highlight in &highlighted_edits.highlights {
23164                assert_eq!(
23165                    highlight.1.background_color,
23166                    Some(cx.theme().status().created_background)
23167                );
23168            }
23169        },
23170    )
23171    .await;
23172}
23173
23174#[gpui::test]
23175async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23176    init_test(cx, |_| {});
23177
23178    // Deletion
23179    assert_highlighted_edits(
23180        "Hello, world!",
23181        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23182        true,
23183        cx,
23184        |highlighted_edits, cx| {
23185            assert_eq!(highlighted_edits.text, "Hello, world!");
23186            assert_eq!(highlighted_edits.highlights.len(), 1);
23187            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23188            assert_eq!(
23189                highlighted_edits.highlights[0].1.background_color,
23190                Some(cx.theme().status().deleted_background)
23191            );
23192        },
23193    )
23194    .await;
23195
23196    // Insertion
23197    assert_highlighted_edits(
23198        "Hello, world!",
23199        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23200        true,
23201        cx,
23202        |highlighted_edits, cx| {
23203            assert_eq!(highlighted_edits.highlights.len(), 1);
23204            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23205            assert_eq!(
23206                highlighted_edits.highlights[0].1.background_color,
23207                Some(cx.theme().status().created_background)
23208            );
23209        },
23210    )
23211    .await;
23212}
23213
23214async fn assert_highlighted_edits(
23215    text: &str,
23216    edits: Vec<(Range<Point>, String)>,
23217    include_deletions: bool,
23218    cx: &mut TestAppContext,
23219    assertion_fn: impl Fn(HighlightedText, &App),
23220) {
23221    let window = cx.add_window(|window, cx| {
23222        let buffer = MultiBuffer::build_simple(text, cx);
23223        Editor::new(EditorMode::full(), buffer, None, window, cx)
23224    });
23225    let cx = &mut VisualTestContext::from_window(*window, cx);
23226
23227    let (buffer, snapshot) = window
23228        .update(cx, |editor, _window, cx| {
23229            (
23230                editor.buffer().clone(),
23231                editor.buffer().read(cx).snapshot(cx),
23232            )
23233        })
23234        .unwrap();
23235
23236    let edits = edits
23237        .into_iter()
23238        .map(|(range, edit)| {
23239            (
23240                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23241                edit,
23242            )
23243        })
23244        .collect::<Vec<_>>();
23245
23246    let text_anchor_edits = edits
23247        .clone()
23248        .into_iter()
23249        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23250        .collect::<Vec<_>>();
23251
23252    let edit_preview = window
23253        .update(cx, |_, _window, cx| {
23254            buffer
23255                .read(cx)
23256                .as_singleton()
23257                .unwrap()
23258                .read(cx)
23259                .preview_edits(text_anchor_edits.into(), cx)
23260        })
23261        .unwrap()
23262        .await;
23263
23264    cx.update(|_window, cx| {
23265        let highlighted_edits = edit_prediction_edit_text(
23266            snapshot.as_singleton().unwrap().2,
23267            &edits,
23268            &edit_preview,
23269            include_deletions,
23270            cx,
23271        );
23272        assertion_fn(highlighted_edits, cx)
23273    });
23274}
23275
23276#[track_caller]
23277fn assert_breakpoint(
23278    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23279    path: &Arc<Path>,
23280    expected: Vec<(u32, Breakpoint)>,
23281) {
23282    if expected.is_empty() {
23283        assert!(!breakpoints.contains_key(path), "{}", path.display());
23284    } else {
23285        let mut breakpoint = breakpoints
23286            .get(path)
23287            .unwrap()
23288            .iter()
23289            .map(|breakpoint| {
23290                (
23291                    breakpoint.row,
23292                    Breakpoint {
23293                        message: breakpoint.message.clone(),
23294                        state: breakpoint.state,
23295                        condition: breakpoint.condition.clone(),
23296                        hit_condition: breakpoint.hit_condition.clone(),
23297                    },
23298                )
23299            })
23300            .collect::<Vec<_>>();
23301
23302        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23303
23304        assert_eq!(expected, breakpoint);
23305    }
23306}
23307
23308fn add_log_breakpoint_at_cursor(
23309    editor: &mut Editor,
23310    log_message: &str,
23311    window: &mut Window,
23312    cx: &mut Context<Editor>,
23313) {
23314    let (anchor, bp) = editor
23315        .breakpoints_at_cursors(window, cx)
23316        .first()
23317        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23318        .unwrap_or_else(|| {
23319            let snapshot = editor.snapshot(window, cx);
23320            let cursor_position: Point =
23321                editor.selections.newest(&snapshot.display_snapshot).head();
23322
23323            let breakpoint_position = snapshot
23324                .buffer_snapshot()
23325                .anchor_before(Point::new(cursor_position.row, 0));
23326
23327            (breakpoint_position, Breakpoint::new_log(log_message))
23328        });
23329
23330    editor.edit_breakpoint_at_anchor(
23331        anchor,
23332        bp,
23333        BreakpointEditAction::EditLogMessage(log_message.into()),
23334        cx,
23335    );
23336}
23337
23338#[gpui::test]
23339async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23340    init_test(cx, |_| {});
23341
23342    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23343    let fs = FakeFs::new(cx.executor());
23344    fs.insert_tree(
23345        path!("/a"),
23346        json!({
23347            "main.rs": sample_text,
23348        }),
23349    )
23350    .await;
23351    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23352    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23353    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23354
23355    let fs = FakeFs::new(cx.executor());
23356    fs.insert_tree(
23357        path!("/a"),
23358        json!({
23359            "main.rs": sample_text,
23360        }),
23361    )
23362    .await;
23363    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23364    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23365    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23366    let worktree_id = workspace
23367        .update(cx, |workspace, _window, cx| {
23368            workspace.project().update(cx, |project, cx| {
23369                project.worktrees(cx).next().unwrap().read(cx).id()
23370            })
23371        })
23372        .unwrap();
23373
23374    let buffer = project
23375        .update(cx, |project, cx| {
23376            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23377        })
23378        .await
23379        .unwrap();
23380
23381    let (editor, cx) = cx.add_window_view(|window, cx| {
23382        Editor::new(
23383            EditorMode::full(),
23384            MultiBuffer::build_from_buffer(buffer, cx),
23385            Some(project.clone()),
23386            window,
23387            cx,
23388        )
23389    });
23390
23391    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23392    let abs_path = project.read_with(cx, |project, cx| {
23393        project
23394            .absolute_path(&project_path, cx)
23395            .map(Arc::from)
23396            .unwrap()
23397    });
23398
23399    // assert we can add breakpoint on the first line
23400    editor.update_in(cx, |editor, window, cx| {
23401        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23402        editor.move_to_end(&MoveToEnd, window, cx);
23403        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23404    });
23405
23406    let breakpoints = editor.update(cx, |editor, cx| {
23407        editor
23408            .breakpoint_store()
23409            .as_ref()
23410            .unwrap()
23411            .read(cx)
23412            .all_source_breakpoints(cx)
23413    });
23414
23415    assert_eq!(1, breakpoints.len());
23416    assert_breakpoint(
23417        &breakpoints,
23418        &abs_path,
23419        vec![
23420            (0, Breakpoint::new_standard()),
23421            (3, Breakpoint::new_standard()),
23422        ],
23423    );
23424
23425    editor.update_in(cx, |editor, window, cx| {
23426        editor.move_to_beginning(&MoveToBeginning, window, cx);
23427        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23428    });
23429
23430    let breakpoints = editor.update(cx, |editor, cx| {
23431        editor
23432            .breakpoint_store()
23433            .as_ref()
23434            .unwrap()
23435            .read(cx)
23436            .all_source_breakpoints(cx)
23437    });
23438
23439    assert_eq!(1, breakpoints.len());
23440    assert_breakpoint(
23441        &breakpoints,
23442        &abs_path,
23443        vec![(3, Breakpoint::new_standard())],
23444    );
23445
23446    editor.update_in(cx, |editor, window, cx| {
23447        editor.move_to_end(&MoveToEnd, window, cx);
23448        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23449    });
23450
23451    let breakpoints = editor.update(cx, |editor, cx| {
23452        editor
23453            .breakpoint_store()
23454            .as_ref()
23455            .unwrap()
23456            .read(cx)
23457            .all_source_breakpoints(cx)
23458    });
23459
23460    assert_eq!(0, breakpoints.len());
23461    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23462}
23463
23464#[gpui::test]
23465async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23466    init_test(cx, |_| {});
23467
23468    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23469
23470    let fs = FakeFs::new(cx.executor());
23471    fs.insert_tree(
23472        path!("/a"),
23473        json!({
23474            "main.rs": sample_text,
23475        }),
23476    )
23477    .await;
23478    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23479    let (workspace, cx) =
23480        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23481
23482    let worktree_id = workspace.update(cx, |workspace, cx| {
23483        workspace.project().update(cx, |project, cx| {
23484            project.worktrees(cx).next().unwrap().read(cx).id()
23485        })
23486    });
23487
23488    let buffer = project
23489        .update(cx, |project, cx| {
23490            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23491        })
23492        .await
23493        .unwrap();
23494
23495    let (editor, cx) = cx.add_window_view(|window, cx| {
23496        Editor::new(
23497            EditorMode::full(),
23498            MultiBuffer::build_from_buffer(buffer, cx),
23499            Some(project.clone()),
23500            window,
23501            cx,
23502        )
23503    });
23504
23505    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23506    let abs_path = project.read_with(cx, |project, cx| {
23507        project
23508            .absolute_path(&project_path, cx)
23509            .map(Arc::from)
23510            .unwrap()
23511    });
23512
23513    editor.update_in(cx, |editor, window, cx| {
23514        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23515    });
23516
23517    let breakpoints = editor.update(cx, |editor, cx| {
23518        editor
23519            .breakpoint_store()
23520            .as_ref()
23521            .unwrap()
23522            .read(cx)
23523            .all_source_breakpoints(cx)
23524    });
23525
23526    assert_breakpoint(
23527        &breakpoints,
23528        &abs_path,
23529        vec![(0, Breakpoint::new_log("hello world"))],
23530    );
23531
23532    // Removing a log message from a log breakpoint should remove it
23533    editor.update_in(cx, |editor, window, cx| {
23534        add_log_breakpoint_at_cursor(editor, "", window, cx);
23535    });
23536
23537    let breakpoints = editor.update(cx, |editor, cx| {
23538        editor
23539            .breakpoint_store()
23540            .as_ref()
23541            .unwrap()
23542            .read(cx)
23543            .all_source_breakpoints(cx)
23544    });
23545
23546    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23547
23548    editor.update_in(cx, |editor, window, cx| {
23549        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23550        editor.move_to_end(&MoveToEnd, window, cx);
23551        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23552        // Not adding a log message to a standard breakpoint shouldn't remove it
23553        add_log_breakpoint_at_cursor(editor, "", window, cx);
23554    });
23555
23556    let breakpoints = editor.update(cx, |editor, cx| {
23557        editor
23558            .breakpoint_store()
23559            .as_ref()
23560            .unwrap()
23561            .read(cx)
23562            .all_source_breakpoints(cx)
23563    });
23564
23565    assert_breakpoint(
23566        &breakpoints,
23567        &abs_path,
23568        vec![
23569            (0, Breakpoint::new_standard()),
23570            (3, Breakpoint::new_standard()),
23571        ],
23572    );
23573
23574    editor.update_in(cx, |editor, window, cx| {
23575        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23576    });
23577
23578    let breakpoints = editor.update(cx, |editor, cx| {
23579        editor
23580            .breakpoint_store()
23581            .as_ref()
23582            .unwrap()
23583            .read(cx)
23584            .all_source_breakpoints(cx)
23585    });
23586
23587    assert_breakpoint(
23588        &breakpoints,
23589        &abs_path,
23590        vec![
23591            (0, Breakpoint::new_standard()),
23592            (3, Breakpoint::new_log("hello world")),
23593        ],
23594    );
23595
23596    editor.update_in(cx, |editor, window, cx| {
23597        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23598    });
23599
23600    let breakpoints = editor.update(cx, |editor, cx| {
23601        editor
23602            .breakpoint_store()
23603            .as_ref()
23604            .unwrap()
23605            .read(cx)
23606            .all_source_breakpoints(cx)
23607    });
23608
23609    assert_breakpoint(
23610        &breakpoints,
23611        &abs_path,
23612        vec![
23613            (0, Breakpoint::new_standard()),
23614            (3, Breakpoint::new_log("hello Earth!!")),
23615        ],
23616    );
23617}
23618
23619/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23620/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23621/// or when breakpoints were placed out of order. This tests for a regression too
23622#[gpui::test]
23623async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23624    init_test(cx, |_| {});
23625
23626    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23627    let fs = FakeFs::new(cx.executor());
23628    fs.insert_tree(
23629        path!("/a"),
23630        json!({
23631            "main.rs": sample_text,
23632        }),
23633    )
23634    .await;
23635    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23636    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23637    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23638
23639    let fs = FakeFs::new(cx.executor());
23640    fs.insert_tree(
23641        path!("/a"),
23642        json!({
23643            "main.rs": sample_text,
23644        }),
23645    )
23646    .await;
23647    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23648    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23649    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23650    let worktree_id = workspace
23651        .update(cx, |workspace, _window, cx| {
23652            workspace.project().update(cx, |project, cx| {
23653                project.worktrees(cx).next().unwrap().read(cx).id()
23654            })
23655        })
23656        .unwrap();
23657
23658    let buffer = project
23659        .update(cx, |project, cx| {
23660            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23661        })
23662        .await
23663        .unwrap();
23664
23665    let (editor, cx) = cx.add_window_view(|window, cx| {
23666        Editor::new(
23667            EditorMode::full(),
23668            MultiBuffer::build_from_buffer(buffer, cx),
23669            Some(project.clone()),
23670            window,
23671            cx,
23672        )
23673    });
23674
23675    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23676    let abs_path = project.read_with(cx, |project, cx| {
23677        project
23678            .absolute_path(&project_path, cx)
23679            .map(Arc::from)
23680            .unwrap()
23681    });
23682
23683    // assert we can add breakpoint on the first line
23684    editor.update_in(cx, |editor, window, cx| {
23685        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23686        editor.move_to_end(&MoveToEnd, window, cx);
23687        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23688        editor.move_up(&MoveUp, window, cx);
23689        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23690    });
23691
23692    let breakpoints = editor.update(cx, |editor, cx| {
23693        editor
23694            .breakpoint_store()
23695            .as_ref()
23696            .unwrap()
23697            .read(cx)
23698            .all_source_breakpoints(cx)
23699    });
23700
23701    assert_eq!(1, breakpoints.len());
23702    assert_breakpoint(
23703        &breakpoints,
23704        &abs_path,
23705        vec![
23706            (0, Breakpoint::new_standard()),
23707            (2, Breakpoint::new_standard()),
23708            (3, Breakpoint::new_standard()),
23709        ],
23710    );
23711
23712    editor.update_in(cx, |editor, window, cx| {
23713        editor.move_to_beginning(&MoveToBeginning, window, cx);
23714        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23715        editor.move_to_end(&MoveToEnd, window, cx);
23716        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23717        // Disabling a breakpoint that doesn't exist should do nothing
23718        editor.move_up(&MoveUp, window, cx);
23719        editor.move_up(&MoveUp, window, cx);
23720        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23721    });
23722
23723    let breakpoints = editor.update(cx, |editor, cx| {
23724        editor
23725            .breakpoint_store()
23726            .as_ref()
23727            .unwrap()
23728            .read(cx)
23729            .all_source_breakpoints(cx)
23730    });
23731
23732    let disable_breakpoint = {
23733        let mut bp = Breakpoint::new_standard();
23734        bp.state = BreakpointState::Disabled;
23735        bp
23736    };
23737
23738    assert_eq!(1, breakpoints.len());
23739    assert_breakpoint(
23740        &breakpoints,
23741        &abs_path,
23742        vec![
23743            (0, disable_breakpoint.clone()),
23744            (2, Breakpoint::new_standard()),
23745            (3, disable_breakpoint.clone()),
23746        ],
23747    );
23748
23749    editor.update_in(cx, |editor, window, cx| {
23750        editor.move_to_beginning(&MoveToBeginning, window, cx);
23751        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23752        editor.move_to_end(&MoveToEnd, window, cx);
23753        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23754        editor.move_up(&MoveUp, window, cx);
23755        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23756    });
23757
23758    let breakpoints = editor.update(cx, |editor, cx| {
23759        editor
23760            .breakpoint_store()
23761            .as_ref()
23762            .unwrap()
23763            .read(cx)
23764            .all_source_breakpoints(cx)
23765    });
23766
23767    assert_eq!(1, breakpoints.len());
23768    assert_breakpoint(
23769        &breakpoints,
23770        &abs_path,
23771        vec![
23772            (0, Breakpoint::new_standard()),
23773            (2, disable_breakpoint),
23774            (3, Breakpoint::new_standard()),
23775        ],
23776    );
23777}
23778
23779#[gpui::test]
23780async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23781    init_test(cx, |_| {});
23782    let capabilities = lsp::ServerCapabilities {
23783        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23784            prepare_provider: Some(true),
23785            work_done_progress_options: Default::default(),
23786        })),
23787        ..Default::default()
23788    };
23789    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23790
23791    cx.set_state(indoc! {"
23792        struct Fˇoo {}
23793    "});
23794
23795    cx.update_editor(|editor, _, cx| {
23796        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23797        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23798        editor.highlight_background::<DocumentHighlightRead>(
23799            &[highlight_range],
23800            |theme| theme.colors().editor_document_highlight_read_background,
23801            cx,
23802        );
23803    });
23804
23805    let mut prepare_rename_handler = cx
23806        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23807            move |_, _, _| async move {
23808                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23809                    start: lsp::Position {
23810                        line: 0,
23811                        character: 7,
23812                    },
23813                    end: lsp::Position {
23814                        line: 0,
23815                        character: 10,
23816                    },
23817                })))
23818            },
23819        );
23820    let prepare_rename_task = cx
23821        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23822        .expect("Prepare rename was not started");
23823    prepare_rename_handler.next().await.unwrap();
23824    prepare_rename_task.await.expect("Prepare rename failed");
23825
23826    let mut rename_handler =
23827        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23828            let edit = lsp::TextEdit {
23829                range: lsp::Range {
23830                    start: lsp::Position {
23831                        line: 0,
23832                        character: 7,
23833                    },
23834                    end: lsp::Position {
23835                        line: 0,
23836                        character: 10,
23837                    },
23838                },
23839                new_text: "FooRenamed".to_string(),
23840            };
23841            Ok(Some(lsp::WorkspaceEdit::new(
23842                // Specify the same edit twice
23843                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23844            )))
23845        });
23846    let rename_task = cx
23847        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23848        .expect("Confirm rename was not started");
23849    rename_handler.next().await.unwrap();
23850    rename_task.await.expect("Confirm rename failed");
23851    cx.run_until_parked();
23852
23853    // Despite two edits, only one is actually applied as those are identical
23854    cx.assert_editor_state(indoc! {"
23855        struct FooRenamedˇ {}
23856    "});
23857}
23858
23859#[gpui::test]
23860async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23861    init_test(cx, |_| {});
23862    // These capabilities indicate that the server does not support prepare rename.
23863    let capabilities = lsp::ServerCapabilities {
23864        rename_provider: Some(lsp::OneOf::Left(true)),
23865        ..Default::default()
23866    };
23867    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23868
23869    cx.set_state(indoc! {"
23870        struct Fˇoo {}
23871    "});
23872
23873    cx.update_editor(|editor, _window, cx| {
23874        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23875        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23876        editor.highlight_background::<DocumentHighlightRead>(
23877            &[highlight_range],
23878            |theme| theme.colors().editor_document_highlight_read_background,
23879            cx,
23880        );
23881    });
23882
23883    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23884        .expect("Prepare rename was not started")
23885        .await
23886        .expect("Prepare rename failed");
23887
23888    let mut rename_handler =
23889        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23890            let edit = lsp::TextEdit {
23891                range: lsp::Range {
23892                    start: lsp::Position {
23893                        line: 0,
23894                        character: 7,
23895                    },
23896                    end: lsp::Position {
23897                        line: 0,
23898                        character: 10,
23899                    },
23900                },
23901                new_text: "FooRenamed".to_string(),
23902            };
23903            Ok(Some(lsp::WorkspaceEdit::new(
23904                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23905            )))
23906        });
23907    let rename_task = cx
23908        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23909        .expect("Confirm rename was not started");
23910    rename_handler.next().await.unwrap();
23911    rename_task.await.expect("Confirm rename failed");
23912    cx.run_until_parked();
23913
23914    // Correct range is renamed, as `surrounding_word` is used to find it.
23915    cx.assert_editor_state(indoc! {"
23916        struct FooRenamedˇ {}
23917    "});
23918}
23919
23920#[gpui::test]
23921async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23922    init_test(cx, |_| {});
23923    let mut cx = EditorTestContext::new(cx).await;
23924
23925    let language = Arc::new(
23926        Language::new(
23927            LanguageConfig::default(),
23928            Some(tree_sitter_html::LANGUAGE.into()),
23929        )
23930        .with_brackets_query(
23931            r#"
23932            ("<" @open "/>" @close)
23933            ("</" @open ">" @close)
23934            ("<" @open ">" @close)
23935            ("\"" @open "\"" @close)
23936            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23937        "#,
23938        )
23939        .unwrap(),
23940    );
23941    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23942
23943    cx.set_state(indoc! {"
23944        <span>ˇ</span>
23945    "});
23946    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23947    cx.assert_editor_state(indoc! {"
23948        <span>
23949        ˇ
23950        </span>
23951    "});
23952
23953    cx.set_state(indoc! {"
23954        <span><span></span>ˇ</span>
23955    "});
23956    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23957    cx.assert_editor_state(indoc! {"
23958        <span><span></span>
23959        ˇ</span>
23960    "});
23961
23962    cx.set_state(indoc! {"
23963        <span>ˇ
23964        </span>
23965    "});
23966    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23967    cx.assert_editor_state(indoc! {"
23968        <span>
23969        ˇ
23970        </span>
23971    "});
23972}
23973
23974#[gpui::test(iterations = 10)]
23975async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23976    init_test(cx, |_| {});
23977
23978    let fs = FakeFs::new(cx.executor());
23979    fs.insert_tree(
23980        path!("/dir"),
23981        json!({
23982            "a.ts": "a",
23983        }),
23984    )
23985    .await;
23986
23987    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23988    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23989    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23990
23991    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23992    language_registry.add(Arc::new(Language::new(
23993        LanguageConfig {
23994            name: "TypeScript".into(),
23995            matcher: LanguageMatcher {
23996                path_suffixes: vec!["ts".to_string()],
23997                ..Default::default()
23998            },
23999            ..Default::default()
24000        },
24001        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24002    )));
24003    let mut fake_language_servers = language_registry.register_fake_lsp(
24004        "TypeScript",
24005        FakeLspAdapter {
24006            capabilities: lsp::ServerCapabilities {
24007                code_lens_provider: Some(lsp::CodeLensOptions {
24008                    resolve_provider: Some(true),
24009                }),
24010                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24011                    commands: vec!["_the/command".to_string()],
24012                    ..lsp::ExecuteCommandOptions::default()
24013                }),
24014                ..lsp::ServerCapabilities::default()
24015            },
24016            ..FakeLspAdapter::default()
24017        },
24018    );
24019
24020    let editor = workspace
24021        .update(cx, |workspace, window, cx| {
24022            workspace.open_abs_path(
24023                PathBuf::from(path!("/dir/a.ts")),
24024                OpenOptions::default(),
24025                window,
24026                cx,
24027            )
24028        })
24029        .unwrap()
24030        .await
24031        .unwrap()
24032        .downcast::<Editor>()
24033        .unwrap();
24034    cx.executor().run_until_parked();
24035
24036    let fake_server = fake_language_servers.next().await.unwrap();
24037
24038    let buffer = editor.update(cx, |editor, cx| {
24039        editor
24040            .buffer()
24041            .read(cx)
24042            .as_singleton()
24043            .expect("have opened a single file by path")
24044    });
24045
24046    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24047    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24048    drop(buffer_snapshot);
24049    let actions = cx
24050        .update_window(*workspace, |_, window, cx| {
24051            project.code_actions(&buffer, anchor..anchor, window, cx)
24052        })
24053        .unwrap();
24054
24055    fake_server
24056        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24057            Ok(Some(vec![
24058                lsp::CodeLens {
24059                    range: lsp::Range::default(),
24060                    command: Some(lsp::Command {
24061                        title: "Code lens command".to_owned(),
24062                        command: "_the/command".to_owned(),
24063                        arguments: None,
24064                    }),
24065                    data: None,
24066                },
24067                lsp::CodeLens {
24068                    range: lsp::Range::default(),
24069                    command: Some(lsp::Command {
24070                        title: "Command not in capabilities".to_owned(),
24071                        command: "not in capabilities".to_owned(),
24072                        arguments: None,
24073                    }),
24074                    data: None,
24075                },
24076                lsp::CodeLens {
24077                    range: lsp::Range {
24078                        start: lsp::Position {
24079                            line: 1,
24080                            character: 1,
24081                        },
24082                        end: lsp::Position {
24083                            line: 1,
24084                            character: 1,
24085                        },
24086                    },
24087                    command: Some(lsp::Command {
24088                        title: "Command not in range".to_owned(),
24089                        command: "_the/command".to_owned(),
24090                        arguments: None,
24091                    }),
24092                    data: None,
24093                },
24094            ]))
24095        })
24096        .next()
24097        .await;
24098
24099    let actions = actions.await.unwrap();
24100    assert_eq!(
24101        actions.len(),
24102        1,
24103        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24104    );
24105    let action = actions[0].clone();
24106    let apply = project.update(cx, |project, cx| {
24107        project.apply_code_action(buffer.clone(), action, true, cx)
24108    });
24109
24110    // Resolving the code action does not populate its edits. In absence of
24111    // edits, we must execute the given command.
24112    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24113        |mut lens, _| async move {
24114            let lens_command = lens.command.as_mut().expect("should have a command");
24115            assert_eq!(lens_command.title, "Code lens command");
24116            lens_command.arguments = Some(vec![json!("the-argument")]);
24117            Ok(lens)
24118        },
24119    );
24120
24121    // While executing the command, the language server sends the editor
24122    // a `workspaceEdit` request.
24123    fake_server
24124        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24125            let fake = fake_server.clone();
24126            move |params, _| {
24127                assert_eq!(params.command, "_the/command");
24128                let fake = fake.clone();
24129                async move {
24130                    fake.server
24131                        .request::<lsp::request::ApplyWorkspaceEdit>(
24132                            lsp::ApplyWorkspaceEditParams {
24133                                label: None,
24134                                edit: lsp::WorkspaceEdit {
24135                                    changes: Some(
24136                                        [(
24137                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24138                                            vec![lsp::TextEdit {
24139                                                range: lsp::Range::new(
24140                                                    lsp::Position::new(0, 0),
24141                                                    lsp::Position::new(0, 0),
24142                                                ),
24143                                                new_text: "X".into(),
24144                                            }],
24145                                        )]
24146                                        .into_iter()
24147                                        .collect(),
24148                                    ),
24149                                    ..lsp::WorkspaceEdit::default()
24150                                },
24151                            },
24152                        )
24153                        .await
24154                        .into_response()
24155                        .unwrap();
24156                    Ok(Some(json!(null)))
24157                }
24158            }
24159        })
24160        .next()
24161        .await;
24162
24163    // Applying the code lens command returns a project transaction containing the edits
24164    // sent by the language server in its `workspaceEdit` request.
24165    let transaction = apply.await.unwrap();
24166    assert!(transaction.0.contains_key(&buffer));
24167    buffer.update(cx, |buffer, cx| {
24168        assert_eq!(buffer.text(), "Xa");
24169        buffer.undo(cx);
24170        assert_eq!(buffer.text(), "a");
24171    });
24172
24173    let actions_after_edits = cx
24174        .update_window(*workspace, |_, window, cx| {
24175            project.code_actions(&buffer, anchor..anchor, window, cx)
24176        })
24177        .unwrap()
24178        .await
24179        .unwrap();
24180    assert_eq!(
24181        actions, actions_after_edits,
24182        "For the same selection, same code lens actions should be returned"
24183    );
24184
24185    let _responses =
24186        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24187            panic!("No more code lens requests are expected");
24188        });
24189    editor.update_in(cx, |editor, window, cx| {
24190        editor.select_all(&SelectAll, window, cx);
24191    });
24192    cx.executor().run_until_parked();
24193    let new_actions = cx
24194        .update_window(*workspace, |_, window, cx| {
24195            project.code_actions(&buffer, anchor..anchor, window, cx)
24196        })
24197        .unwrap()
24198        .await
24199        .unwrap();
24200    assert_eq!(
24201        actions, new_actions,
24202        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24203    );
24204}
24205
24206#[gpui::test]
24207async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24208    init_test(cx, |_| {});
24209
24210    let fs = FakeFs::new(cx.executor());
24211    let main_text = r#"fn main() {
24212println!("1");
24213println!("2");
24214println!("3");
24215println!("4");
24216println!("5");
24217}"#;
24218    let lib_text = "mod foo {}";
24219    fs.insert_tree(
24220        path!("/a"),
24221        json!({
24222            "lib.rs": lib_text,
24223            "main.rs": main_text,
24224        }),
24225    )
24226    .await;
24227
24228    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24229    let (workspace, cx) =
24230        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24231    let worktree_id = workspace.update(cx, |workspace, cx| {
24232        workspace.project().update(cx, |project, cx| {
24233            project.worktrees(cx).next().unwrap().read(cx).id()
24234        })
24235    });
24236
24237    let expected_ranges = vec![
24238        Point::new(0, 0)..Point::new(0, 0),
24239        Point::new(1, 0)..Point::new(1, 1),
24240        Point::new(2, 0)..Point::new(2, 2),
24241        Point::new(3, 0)..Point::new(3, 3),
24242    ];
24243
24244    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24245    let editor_1 = workspace
24246        .update_in(cx, |workspace, window, cx| {
24247            workspace.open_path(
24248                (worktree_id, rel_path("main.rs")),
24249                Some(pane_1.downgrade()),
24250                true,
24251                window,
24252                cx,
24253            )
24254        })
24255        .unwrap()
24256        .await
24257        .downcast::<Editor>()
24258        .unwrap();
24259    pane_1.update(cx, |pane, cx| {
24260        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24261        open_editor.update(cx, |editor, cx| {
24262            assert_eq!(
24263                editor.display_text(cx),
24264                main_text,
24265                "Original main.rs text on initial open",
24266            );
24267            assert_eq!(
24268                editor
24269                    .selections
24270                    .all::<Point>(&editor.display_snapshot(cx))
24271                    .into_iter()
24272                    .map(|s| s.range())
24273                    .collect::<Vec<_>>(),
24274                vec![Point::zero()..Point::zero()],
24275                "Default selections on initial open",
24276            );
24277        })
24278    });
24279    editor_1.update_in(cx, |editor, window, cx| {
24280        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24281            s.select_ranges(expected_ranges.clone());
24282        });
24283    });
24284
24285    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24286        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24287    });
24288    let editor_2 = workspace
24289        .update_in(cx, |workspace, window, cx| {
24290            workspace.open_path(
24291                (worktree_id, rel_path("main.rs")),
24292                Some(pane_2.downgrade()),
24293                true,
24294                window,
24295                cx,
24296            )
24297        })
24298        .unwrap()
24299        .await
24300        .downcast::<Editor>()
24301        .unwrap();
24302    pane_2.update(cx, |pane, cx| {
24303        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24304        open_editor.update(cx, |editor, cx| {
24305            assert_eq!(
24306                editor.display_text(cx),
24307                main_text,
24308                "Original main.rs text on initial open in another panel",
24309            );
24310            assert_eq!(
24311                editor
24312                    .selections
24313                    .all::<Point>(&editor.display_snapshot(cx))
24314                    .into_iter()
24315                    .map(|s| s.range())
24316                    .collect::<Vec<_>>(),
24317                vec![Point::zero()..Point::zero()],
24318                "Default selections on initial open in another panel",
24319            );
24320        })
24321    });
24322
24323    editor_2.update_in(cx, |editor, window, cx| {
24324        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24325    });
24326
24327    let _other_editor_1 = workspace
24328        .update_in(cx, |workspace, window, cx| {
24329            workspace.open_path(
24330                (worktree_id, rel_path("lib.rs")),
24331                Some(pane_1.downgrade()),
24332                true,
24333                window,
24334                cx,
24335            )
24336        })
24337        .unwrap()
24338        .await
24339        .downcast::<Editor>()
24340        .unwrap();
24341    pane_1
24342        .update_in(cx, |pane, window, cx| {
24343            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24344        })
24345        .await
24346        .unwrap();
24347    drop(editor_1);
24348    pane_1.update(cx, |pane, cx| {
24349        pane.active_item()
24350            .unwrap()
24351            .downcast::<Editor>()
24352            .unwrap()
24353            .update(cx, |editor, cx| {
24354                assert_eq!(
24355                    editor.display_text(cx),
24356                    lib_text,
24357                    "Other file should be open and active",
24358                );
24359            });
24360        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24361    });
24362
24363    let _other_editor_2 = workspace
24364        .update_in(cx, |workspace, window, cx| {
24365            workspace.open_path(
24366                (worktree_id, rel_path("lib.rs")),
24367                Some(pane_2.downgrade()),
24368                true,
24369                window,
24370                cx,
24371            )
24372        })
24373        .unwrap()
24374        .await
24375        .downcast::<Editor>()
24376        .unwrap();
24377    pane_2
24378        .update_in(cx, |pane, window, cx| {
24379            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24380        })
24381        .await
24382        .unwrap();
24383    drop(editor_2);
24384    pane_2.update(cx, |pane, cx| {
24385        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24386        open_editor.update(cx, |editor, cx| {
24387            assert_eq!(
24388                editor.display_text(cx),
24389                lib_text,
24390                "Other file should be open and active in another panel too",
24391            );
24392        });
24393        assert_eq!(
24394            pane.items().count(),
24395            1,
24396            "No other editors should be open in another pane",
24397        );
24398    });
24399
24400    let _editor_1_reopened = workspace
24401        .update_in(cx, |workspace, window, cx| {
24402            workspace.open_path(
24403                (worktree_id, rel_path("main.rs")),
24404                Some(pane_1.downgrade()),
24405                true,
24406                window,
24407                cx,
24408            )
24409        })
24410        .unwrap()
24411        .await
24412        .downcast::<Editor>()
24413        .unwrap();
24414    let _editor_2_reopened = workspace
24415        .update_in(cx, |workspace, window, cx| {
24416            workspace.open_path(
24417                (worktree_id, rel_path("main.rs")),
24418                Some(pane_2.downgrade()),
24419                true,
24420                window,
24421                cx,
24422            )
24423        })
24424        .unwrap()
24425        .await
24426        .downcast::<Editor>()
24427        .unwrap();
24428    pane_1.update(cx, |pane, cx| {
24429        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24430        open_editor.update(cx, |editor, cx| {
24431            assert_eq!(
24432                editor.display_text(cx),
24433                main_text,
24434                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24435            );
24436            assert_eq!(
24437                editor
24438                    .selections
24439                    .all::<Point>(&editor.display_snapshot(cx))
24440                    .into_iter()
24441                    .map(|s| s.range())
24442                    .collect::<Vec<_>>(),
24443                expected_ranges,
24444                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24445            );
24446        })
24447    });
24448    pane_2.update(cx, |pane, cx| {
24449        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24450        open_editor.update(cx, |editor, cx| {
24451            assert_eq!(
24452                editor.display_text(cx),
24453                r#"fn main() {
24454⋯rintln!("1");
24455⋯intln!("2");
24456⋯ntln!("3");
24457println!("4");
24458println!("5");
24459}"#,
24460                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24461            );
24462            assert_eq!(
24463                editor
24464                    .selections
24465                    .all::<Point>(&editor.display_snapshot(cx))
24466                    .into_iter()
24467                    .map(|s| s.range())
24468                    .collect::<Vec<_>>(),
24469                vec![Point::zero()..Point::zero()],
24470                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24471            );
24472        })
24473    });
24474}
24475
24476#[gpui::test]
24477async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24478    init_test(cx, |_| {});
24479
24480    let fs = FakeFs::new(cx.executor());
24481    let main_text = r#"fn main() {
24482println!("1");
24483println!("2");
24484println!("3");
24485println!("4");
24486println!("5");
24487}"#;
24488    let lib_text = "mod foo {}";
24489    fs.insert_tree(
24490        path!("/a"),
24491        json!({
24492            "lib.rs": lib_text,
24493            "main.rs": main_text,
24494        }),
24495    )
24496    .await;
24497
24498    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24499    let (workspace, cx) =
24500        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24501    let worktree_id = workspace.update(cx, |workspace, cx| {
24502        workspace.project().update(cx, |project, cx| {
24503            project.worktrees(cx).next().unwrap().read(cx).id()
24504        })
24505    });
24506
24507    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24508    let editor = workspace
24509        .update_in(cx, |workspace, window, cx| {
24510            workspace.open_path(
24511                (worktree_id, rel_path("main.rs")),
24512                Some(pane.downgrade()),
24513                true,
24514                window,
24515                cx,
24516            )
24517        })
24518        .unwrap()
24519        .await
24520        .downcast::<Editor>()
24521        .unwrap();
24522    pane.update(cx, |pane, cx| {
24523        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24524        open_editor.update(cx, |editor, cx| {
24525            assert_eq!(
24526                editor.display_text(cx),
24527                main_text,
24528                "Original main.rs text on initial open",
24529            );
24530        })
24531    });
24532    editor.update_in(cx, |editor, window, cx| {
24533        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24534    });
24535
24536    cx.update_global(|store: &mut SettingsStore, cx| {
24537        store.update_user_settings(cx, |s| {
24538            s.workspace.restore_on_file_reopen = Some(false);
24539        });
24540    });
24541    editor.update_in(cx, |editor, window, cx| {
24542        editor.fold_ranges(
24543            vec![
24544                Point::new(1, 0)..Point::new(1, 1),
24545                Point::new(2, 0)..Point::new(2, 2),
24546                Point::new(3, 0)..Point::new(3, 3),
24547            ],
24548            false,
24549            window,
24550            cx,
24551        );
24552    });
24553    pane.update_in(cx, |pane, window, cx| {
24554        pane.close_all_items(&CloseAllItems::default(), window, cx)
24555    })
24556    .await
24557    .unwrap();
24558    pane.update(cx, |pane, _| {
24559        assert!(pane.active_item().is_none());
24560    });
24561    cx.update_global(|store: &mut SettingsStore, cx| {
24562        store.update_user_settings(cx, |s| {
24563            s.workspace.restore_on_file_reopen = Some(true);
24564        });
24565    });
24566
24567    let _editor_reopened = workspace
24568        .update_in(cx, |workspace, window, cx| {
24569            workspace.open_path(
24570                (worktree_id, rel_path("main.rs")),
24571                Some(pane.downgrade()),
24572                true,
24573                window,
24574                cx,
24575            )
24576        })
24577        .unwrap()
24578        .await
24579        .downcast::<Editor>()
24580        .unwrap();
24581    pane.update(cx, |pane, cx| {
24582        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24583        open_editor.update(cx, |editor, cx| {
24584            assert_eq!(
24585                editor.display_text(cx),
24586                main_text,
24587                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24588            );
24589        })
24590    });
24591}
24592
24593#[gpui::test]
24594async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24595    struct EmptyModalView {
24596        focus_handle: gpui::FocusHandle,
24597    }
24598    impl EventEmitter<DismissEvent> for EmptyModalView {}
24599    impl Render for EmptyModalView {
24600        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24601            div()
24602        }
24603    }
24604    impl Focusable for EmptyModalView {
24605        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24606            self.focus_handle.clone()
24607        }
24608    }
24609    impl workspace::ModalView for EmptyModalView {}
24610    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24611        EmptyModalView {
24612            focus_handle: cx.focus_handle(),
24613        }
24614    }
24615
24616    init_test(cx, |_| {});
24617
24618    let fs = FakeFs::new(cx.executor());
24619    let project = Project::test(fs, [], cx).await;
24620    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24621    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24622    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24623    let editor = cx.new_window_entity(|window, cx| {
24624        Editor::new(
24625            EditorMode::full(),
24626            buffer,
24627            Some(project.clone()),
24628            window,
24629            cx,
24630        )
24631    });
24632    workspace
24633        .update(cx, |workspace, window, cx| {
24634            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24635        })
24636        .unwrap();
24637    editor.update_in(cx, |editor, window, cx| {
24638        editor.open_context_menu(&OpenContextMenu, window, cx);
24639        assert!(editor.mouse_context_menu.is_some());
24640    });
24641    workspace
24642        .update(cx, |workspace, window, cx| {
24643            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24644        })
24645        .unwrap();
24646    cx.read(|cx| {
24647        assert!(editor.read(cx).mouse_context_menu.is_none());
24648    });
24649}
24650
24651fn set_linked_edit_ranges(
24652    opening: (Point, Point),
24653    closing: (Point, Point),
24654    editor: &mut Editor,
24655    cx: &mut Context<Editor>,
24656) {
24657    let Some((buffer, _)) = editor
24658        .buffer
24659        .read(cx)
24660        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24661    else {
24662        panic!("Failed to get buffer for selection position");
24663    };
24664    let buffer = buffer.read(cx);
24665    let buffer_id = buffer.remote_id();
24666    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24667    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24668    let mut linked_ranges = HashMap::default();
24669    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24670    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24671}
24672
24673#[gpui::test]
24674async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24675    init_test(cx, |_| {});
24676
24677    let fs = FakeFs::new(cx.executor());
24678    fs.insert_file(path!("/file.html"), Default::default())
24679        .await;
24680
24681    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24682
24683    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24684    let html_language = Arc::new(Language::new(
24685        LanguageConfig {
24686            name: "HTML".into(),
24687            matcher: LanguageMatcher {
24688                path_suffixes: vec!["html".to_string()],
24689                ..LanguageMatcher::default()
24690            },
24691            brackets: BracketPairConfig {
24692                pairs: vec![BracketPair {
24693                    start: "<".into(),
24694                    end: ">".into(),
24695                    close: true,
24696                    ..Default::default()
24697                }],
24698                ..Default::default()
24699            },
24700            ..Default::default()
24701        },
24702        Some(tree_sitter_html::LANGUAGE.into()),
24703    ));
24704    language_registry.add(html_language);
24705    let mut fake_servers = language_registry.register_fake_lsp(
24706        "HTML",
24707        FakeLspAdapter {
24708            capabilities: lsp::ServerCapabilities {
24709                completion_provider: Some(lsp::CompletionOptions {
24710                    resolve_provider: Some(true),
24711                    ..Default::default()
24712                }),
24713                ..Default::default()
24714            },
24715            ..Default::default()
24716        },
24717    );
24718
24719    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24720    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24721
24722    let worktree_id = workspace
24723        .update(cx, |workspace, _window, cx| {
24724            workspace.project().update(cx, |project, cx| {
24725                project.worktrees(cx).next().unwrap().read(cx).id()
24726            })
24727        })
24728        .unwrap();
24729    project
24730        .update(cx, |project, cx| {
24731            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24732        })
24733        .await
24734        .unwrap();
24735    let editor = workspace
24736        .update(cx, |workspace, window, cx| {
24737            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24738        })
24739        .unwrap()
24740        .await
24741        .unwrap()
24742        .downcast::<Editor>()
24743        .unwrap();
24744
24745    let fake_server = fake_servers.next().await.unwrap();
24746    editor.update_in(cx, |editor, window, cx| {
24747        editor.set_text("<ad></ad>", window, cx);
24748        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24749            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24750        });
24751        set_linked_edit_ranges(
24752            (Point::new(0, 1), Point::new(0, 3)),
24753            (Point::new(0, 6), Point::new(0, 8)),
24754            editor,
24755            cx,
24756        );
24757    });
24758    let mut completion_handle =
24759        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24760            Ok(Some(lsp::CompletionResponse::Array(vec![
24761                lsp::CompletionItem {
24762                    label: "head".to_string(),
24763                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24764                        lsp::InsertReplaceEdit {
24765                            new_text: "head".to_string(),
24766                            insert: lsp::Range::new(
24767                                lsp::Position::new(0, 1),
24768                                lsp::Position::new(0, 3),
24769                            ),
24770                            replace: lsp::Range::new(
24771                                lsp::Position::new(0, 1),
24772                                lsp::Position::new(0, 3),
24773                            ),
24774                        },
24775                    )),
24776                    ..Default::default()
24777                },
24778            ])))
24779        });
24780    editor.update_in(cx, |editor, window, cx| {
24781        editor.show_completions(&ShowCompletions, window, cx);
24782    });
24783    cx.run_until_parked();
24784    completion_handle.next().await.unwrap();
24785    editor.update(cx, |editor, _| {
24786        assert!(
24787            editor.context_menu_visible(),
24788            "Completion menu should be visible"
24789        );
24790    });
24791    editor.update_in(cx, |editor, window, cx| {
24792        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24793    });
24794    cx.executor().run_until_parked();
24795    editor.update(cx, |editor, cx| {
24796        assert_eq!(editor.text(cx), "<head></head>");
24797    });
24798}
24799
24800#[gpui::test]
24801async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24802    init_test(cx, |_| {});
24803
24804    let mut cx = EditorTestContext::new(cx).await;
24805    let language = Arc::new(Language::new(
24806        LanguageConfig {
24807            name: "TSX".into(),
24808            matcher: LanguageMatcher {
24809                path_suffixes: vec!["tsx".to_string()],
24810                ..LanguageMatcher::default()
24811            },
24812            brackets: BracketPairConfig {
24813                pairs: vec![BracketPair {
24814                    start: "<".into(),
24815                    end: ">".into(),
24816                    close: true,
24817                    ..Default::default()
24818                }],
24819                ..Default::default()
24820            },
24821            linked_edit_characters: HashSet::from_iter(['.']),
24822            ..Default::default()
24823        },
24824        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24825    ));
24826    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24827
24828    // Test typing > does not extend linked pair
24829    cx.set_state("<divˇ<div></div>");
24830    cx.update_editor(|editor, _, cx| {
24831        set_linked_edit_ranges(
24832            (Point::new(0, 1), Point::new(0, 4)),
24833            (Point::new(0, 11), Point::new(0, 14)),
24834            editor,
24835            cx,
24836        );
24837    });
24838    cx.update_editor(|editor, window, cx| {
24839        editor.handle_input(">", window, cx);
24840    });
24841    cx.assert_editor_state("<div>ˇ<div></div>");
24842
24843    // Test typing . do extend linked pair
24844    cx.set_state("<Animatedˇ></Animated>");
24845    cx.update_editor(|editor, _, cx| {
24846        set_linked_edit_ranges(
24847            (Point::new(0, 1), Point::new(0, 9)),
24848            (Point::new(0, 12), Point::new(0, 20)),
24849            editor,
24850            cx,
24851        );
24852    });
24853    cx.update_editor(|editor, window, cx| {
24854        editor.handle_input(".", window, cx);
24855    });
24856    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24857    cx.update_editor(|editor, _, cx| {
24858        set_linked_edit_ranges(
24859            (Point::new(0, 1), Point::new(0, 10)),
24860            (Point::new(0, 13), Point::new(0, 21)),
24861            editor,
24862            cx,
24863        );
24864    });
24865    cx.update_editor(|editor, window, cx| {
24866        editor.handle_input("V", window, cx);
24867    });
24868    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24869}
24870
24871#[gpui::test]
24872async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24873    init_test(cx, |_| {});
24874
24875    let fs = FakeFs::new(cx.executor());
24876    fs.insert_tree(
24877        path!("/root"),
24878        json!({
24879            "a": {
24880                "main.rs": "fn main() {}",
24881            },
24882            "foo": {
24883                "bar": {
24884                    "external_file.rs": "pub mod external {}",
24885                }
24886            }
24887        }),
24888    )
24889    .await;
24890
24891    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24892    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24893    language_registry.add(rust_lang());
24894    let _fake_servers = language_registry.register_fake_lsp(
24895        "Rust",
24896        FakeLspAdapter {
24897            ..FakeLspAdapter::default()
24898        },
24899    );
24900    let (workspace, cx) =
24901        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24902    let worktree_id = workspace.update(cx, |workspace, cx| {
24903        workspace.project().update(cx, |project, cx| {
24904            project.worktrees(cx).next().unwrap().read(cx).id()
24905        })
24906    });
24907
24908    let assert_language_servers_count =
24909        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24910            project.update(cx, |project, cx| {
24911                let current = project
24912                    .lsp_store()
24913                    .read(cx)
24914                    .as_local()
24915                    .unwrap()
24916                    .language_servers
24917                    .len();
24918                assert_eq!(expected, current, "{context}");
24919            });
24920        };
24921
24922    assert_language_servers_count(
24923        0,
24924        "No servers should be running before any file is open",
24925        cx,
24926    );
24927    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24928    let main_editor = workspace
24929        .update_in(cx, |workspace, window, cx| {
24930            workspace.open_path(
24931                (worktree_id, rel_path("main.rs")),
24932                Some(pane.downgrade()),
24933                true,
24934                window,
24935                cx,
24936            )
24937        })
24938        .unwrap()
24939        .await
24940        .downcast::<Editor>()
24941        .unwrap();
24942    pane.update(cx, |pane, cx| {
24943        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24944        open_editor.update(cx, |editor, cx| {
24945            assert_eq!(
24946                editor.display_text(cx),
24947                "fn main() {}",
24948                "Original main.rs text on initial open",
24949            );
24950        });
24951        assert_eq!(open_editor, main_editor);
24952    });
24953    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24954
24955    let external_editor = workspace
24956        .update_in(cx, |workspace, window, cx| {
24957            workspace.open_abs_path(
24958                PathBuf::from("/root/foo/bar/external_file.rs"),
24959                OpenOptions::default(),
24960                window,
24961                cx,
24962            )
24963        })
24964        .await
24965        .expect("opening external file")
24966        .downcast::<Editor>()
24967        .expect("downcasted external file's open element to editor");
24968    pane.update(cx, |pane, cx| {
24969        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24970        open_editor.update(cx, |editor, cx| {
24971            assert_eq!(
24972                editor.display_text(cx),
24973                "pub mod external {}",
24974                "External file is open now",
24975            );
24976        });
24977        assert_eq!(open_editor, external_editor);
24978    });
24979    assert_language_servers_count(
24980        1,
24981        "Second, external, *.rs file should join the existing server",
24982        cx,
24983    );
24984
24985    pane.update_in(cx, |pane, window, cx| {
24986        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24987    })
24988    .await
24989    .unwrap();
24990    pane.update_in(cx, |pane, window, cx| {
24991        pane.navigate_backward(&Default::default(), window, cx);
24992    });
24993    cx.run_until_parked();
24994    pane.update(cx, |pane, cx| {
24995        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24996        open_editor.update(cx, |editor, cx| {
24997            assert_eq!(
24998                editor.display_text(cx),
24999                "pub mod external {}",
25000                "External file is open now",
25001            );
25002        });
25003    });
25004    assert_language_servers_count(
25005        1,
25006        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25007        cx,
25008    );
25009
25010    cx.update(|_, cx| {
25011        workspace::reload(cx);
25012    });
25013    assert_language_servers_count(
25014        1,
25015        "After reloading the worktree with local and external files opened, only one project should be started",
25016        cx,
25017    );
25018}
25019
25020#[gpui::test]
25021async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25022    init_test(cx, |_| {});
25023
25024    let mut cx = EditorTestContext::new(cx).await;
25025    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25026    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25027
25028    // test cursor move to start of each line on tab
25029    // for `if`, `elif`, `else`, `while`, `with` and `for`
25030    cx.set_state(indoc! {"
25031        def main():
25032        ˇ    for item in items:
25033        ˇ        while item.active:
25034        ˇ            if item.value > 10:
25035        ˇ                continue
25036        ˇ            elif item.value < 0:
25037        ˇ                break
25038        ˇ            else:
25039        ˇ                with item.context() as ctx:
25040        ˇ                    yield count
25041        ˇ        else:
25042        ˇ            log('while else')
25043        ˇ    else:
25044        ˇ        log('for else')
25045    "});
25046    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25047    cx.assert_editor_state(indoc! {"
25048        def main():
25049            ˇfor item in items:
25050                ˇwhile item.active:
25051                    ˇif item.value > 10:
25052                        ˇcontinue
25053                    ˇelif item.value < 0:
25054                        ˇbreak
25055                    ˇelse:
25056                        ˇwith item.context() as ctx:
25057                            ˇyield count
25058                ˇelse:
25059                    ˇlog('while else')
25060            ˇelse:
25061                ˇlog('for else')
25062    "});
25063    // test relative indent is preserved when tab
25064    // for `if`, `elif`, `else`, `while`, `with` and `for`
25065    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25066    cx.assert_editor_state(indoc! {"
25067        def main():
25068                ˇfor item in items:
25069                    ˇwhile item.active:
25070                        ˇif item.value > 10:
25071                            ˇcontinue
25072                        ˇelif item.value < 0:
25073                            ˇbreak
25074                        ˇelse:
25075                            ˇwith item.context() as ctx:
25076                                ˇyield count
25077                    ˇelse:
25078                        ˇlog('while else')
25079                ˇelse:
25080                    ˇlog('for else')
25081    "});
25082
25083    // test cursor move to start of each line on tab
25084    // for `try`, `except`, `else`, `finally`, `match` and `def`
25085    cx.set_state(indoc! {"
25086        def main():
25087        ˇ    try:
25088        ˇ        fetch()
25089        ˇ    except ValueError:
25090        ˇ        handle_error()
25091        ˇ    else:
25092        ˇ        match value:
25093        ˇ            case _:
25094        ˇ    finally:
25095        ˇ        def status():
25096        ˇ            return 0
25097    "});
25098    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25099    cx.assert_editor_state(indoc! {"
25100        def main():
25101            ˇtry:
25102                ˇfetch()
25103            ˇexcept ValueError:
25104                ˇhandle_error()
25105            ˇelse:
25106                ˇmatch value:
25107                    ˇcase _:
25108            ˇfinally:
25109                ˇdef status():
25110                    ˇreturn 0
25111    "});
25112    // test relative indent is preserved when tab
25113    // for `try`, `except`, `else`, `finally`, `match` and `def`
25114    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25115    cx.assert_editor_state(indoc! {"
25116        def main():
25117                ˇtry:
25118                    ˇfetch()
25119                ˇexcept ValueError:
25120                    ˇhandle_error()
25121                ˇelse:
25122                    ˇmatch value:
25123                        ˇcase _:
25124                ˇfinally:
25125                    ˇdef status():
25126                        ˇreturn 0
25127    "});
25128}
25129
25130#[gpui::test]
25131async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25132    init_test(cx, |_| {});
25133
25134    let mut cx = EditorTestContext::new(cx).await;
25135    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25136    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25137
25138    // test `else` auto outdents when typed inside `if` block
25139    cx.set_state(indoc! {"
25140        def main():
25141            if i == 2:
25142                return
25143                ˇ
25144    "});
25145    cx.update_editor(|editor, window, cx| {
25146        editor.handle_input("else:", window, cx);
25147    });
25148    cx.assert_editor_state(indoc! {"
25149        def main():
25150            if i == 2:
25151                return
25152            else:ˇ
25153    "});
25154
25155    // test `except` auto outdents when typed inside `try` block
25156    cx.set_state(indoc! {"
25157        def main():
25158            try:
25159                i = 2
25160                ˇ
25161    "});
25162    cx.update_editor(|editor, window, cx| {
25163        editor.handle_input("except:", window, cx);
25164    });
25165    cx.assert_editor_state(indoc! {"
25166        def main():
25167            try:
25168                i = 2
25169            except:ˇ
25170    "});
25171
25172    // test `else` auto outdents when typed inside `except` block
25173    cx.set_state(indoc! {"
25174        def main():
25175            try:
25176                i = 2
25177            except:
25178                j = 2
25179                ˇ
25180    "});
25181    cx.update_editor(|editor, window, cx| {
25182        editor.handle_input("else:", window, cx);
25183    });
25184    cx.assert_editor_state(indoc! {"
25185        def main():
25186            try:
25187                i = 2
25188            except:
25189                j = 2
25190            else:ˇ
25191    "});
25192
25193    // test `finally` auto outdents when typed inside `else` block
25194    cx.set_state(indoc! {"
25195        def main():
25196            try:
25197                i = 2
25198            except:
25199                j = 2
25200            else:
25201                k = 2
25202                ˇ
25203    "});
25204    cx.update_editor(|editor, window, cx| {
25205        editor.handle_input("finally:", window, cx);
25206    });
25207    cx.assert_editor_state(indoc! {"
25208        def main():
25209            try:
25210                i = 2
25211            except:
25212                j = 2
25213            else:
25214                k = 2
25215            finally:ˇ
25216    "});
25217
25218    // test `else` does not outdents when typed inside `except` block right after for block
25219    cx.set_state(indoc! {"
25220        def main():
25221            try:
25222                i = 2
25223            except:
25224                for i in range(n):
25225                    pass
25226                ˇ
25227    "});
25228    cx.update_editor(|editor, window, cx| {
25229        editor.handle_input("else:", window, cx);
25230    });
25231    cx.assert_editor_state(indoc! {"
25232        def main():
25233            try:
25234                i = 2
25235            except:
25236                for i in range(n):
25237                    pass
25238                else:ˇ
25239    "});
25240
25241    // test `finally` auto outdents when typed inside `else` block right after for block
25242    cx.set_state(indoc! {"
25243        def main():
25244            try:
25245                i = 2
25246            except:
25247                j = 2
25248            else:
25249                for i in range(n):
25250                    pass
25251                ˇ
25252    "});
25253    cx.update_editor(|editor, window, cx| {
25254        editor.handle_input("finally:", window, cx);
25255    });
25256    cx.assert_editor_state(indoc! {"
25257        def main():
25258            try:
25259                i = 2
25260            except:
25261                j = 2
25262            else:
25263                for i in range(n):
25264                    pass
25265            finally:ˇ
25266    "});
25267
25268    // test `except` outdents to inner "try" block
25269    cx.set_state(indoc! {"
25270        def main():
25271            try:
25272                i = 2
25273                if i == 2:
25274                    try:
25275                        i = 3
25276                        ˇ
25277    "});
25278    cx.update_editor(|editor, window, cx| {
25279        editor.handle_input("except:", window, cx);
25280    });
25281    cx.assert_editor_state(indoc! {"
25282        def main():
25283            try:
25284                i = 2
25285                if i == 2:
25286                    try:
25287                        i = 3
25288                    except:ˇ
25289    "});
25290
25291    // test `except` outdents to outer "try" block
25292    cx.set_state(indoc! {"
25293        def main():
25294            try:
25295                i = 2
25296                if i == 2:
25297                    try:
25298                        i = 3
25299                ˇ
25300    "});
25301    cx.update_editor(|editor, window, cx| {
25302        editor.handle_input("except:", window, cx);
25303    });
25304    cx.assert_editor_state(indoc! {"
25305        def main():
25306            try:
25307                i = 2
25308                if i == 2:
25309                    try:
25310                        i = 3
25311            except:ˇ
25312    "});
25313
25314    // test `else` stays at correct indent when typed after `for` block
25315    cx.set_state(indoc! {"
25316        def main():
25317            for i in range(10):
25318                if i == 3:
25319                    break
25320            ˇ
25321    "});
25322    cx.update_editor(|editor, window, cx| {
25323        editor.handle_input("else:", window, cx);
25324    });
25325    cx.assert_editor_state(indoc! {"
25326        def main():
25327            for i in range(10):
25328                if i == 3:
25329                    break
25330            else:ˇ
25331    "});
25332
25333    // test does not outdent on typing after line with square brackets
25334    cx.set_state(indoc! {"
25335        def f() -> list[str]:
25336            ˇ
25337    "});
25338    cx.update_editor(|editor, window, cx| {
25339        editor.handle_input("a", window, cx);
25340    });
25341    cx.assert_editor_state(indoc! {"
25342        def f() -> list[str]:
2534325344    "});
25345
25346    // test does not outdent on typing : after case keyword
25347    cx.set_state(indoc! {"
25348        match 1:
25349            caseˇ
25350    "});
25351    cx.update_editor(|editor, window, cx| {
25352        editor.handle_input(":", window, cx);
25353    });
25354    cx.assert_editor_state(indoc! {"
25355        match 1:
25356            case:ˇ
25357    "});
25358}
25359
25360#[gpui::test]
25361async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25362    init_test(cx, |_| {});
25363    update_test_language_settings(cx, |settings| {
25364        settings.defaults.extend_comment_on_newline = Some(false);
25365    });
25366    let mut cx = EditorTestContext::new(cx).await;
25367    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25368    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25369
25370    // test correct indent after newline on comment
25371    cx.set_state(indoc! {"
25372        # COMMENT:ˇ
25373    "});
25374    cx.update_editor(|editor, window, cx| {
25375        editor.newline(&Newline, window, cx);
25376    });
25377    cx.assert_editor_state(indoc! {"
25378        # COMMENT:
25379        ˇ
25380    "});
25381
25382    // test correct indent after newline in brackets
25383    cx.set_state(indoc! {"
25384        {ˇ}
25385    "});
25386    cx.update_editor(|editor, window, cx| {
25387        editor.newline(&Newline, window, cx);
25388    });
25389    cx.run_until_parked();
25390    cx.assert_editor_state(indoc! {"
25391        {
25392            ˇ
25393        }
25394    "});
25395
25396    cx.set_state(indoc! {"
25397        (ˇ)
25398    "});
25399    cx.update_editor(|editor, window, cx| {
25400        editor.newline(&Newline, window, cx);
25401    });
25402    cx.run_until_parked();
25403    cx.assert_editor_state(indoc! {"
25404        (
25405            ˇ
25406        )
25407    "});
25408
25409    // do not indent after empty lists or dictionaries
25410    cx.set_state(indoc! {"
25411        a = []ˇ
25412    "});
25413    cx.update_editor(|editor, window, cx| {
25414        editor.newline(&Newline, window, cx);
25415    });
25416    cx.run_until_parked();
25417    cx.assert_editor_state(indoc! {"
25418        a = []
25419        ˇ
25420    "});
25421}
25422
25423#[gpui::test]
25424async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25425    init_test(cx, |_| {});
25426
25427    let mut cx = EditorTestContext::new(cx).await;
25428    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25429    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25430
25431    // test cursor move to start of each line on tab
25432    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25433    cx.set_state(indoc! {"
25434        function main() {
25435        ˇ    for item in $items; do
25436        ˇ        while [ -n \"$item\" ]; do
25437        ˇ            if [ \"$value\" -gt 10 ]; then
25438        ˇ                continue
25439        ˇ            elif [ \"$value\" -lt 0 ]; then
25440        ˇ                break
25441        ˇ            else
25442        ˇ                echo \"$item\"
25443        ˇ            fi
25444        ˇ        done
25445        ˇ    done
25446        ˇ}
25447    "});
25448    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25449    cx.assert_editor_state(indoc! {"
25450        function main() {
25451            ˇfor item in $items; do
25452                ˇwhile [ -n \"$item\" ]; do
25453                    ˇif [ \"$value\" -gt 10 ]; then
25454                        ˇcontinue
25455                    ˇelif [ \"$value\" -lt 0 ]; then
25456                        ˇbreak
25457                    ˇelse
25458                        ˇecho \"$item\"
25459                    ˇfi
25460                ˇdone
25461            ˇdone
25462        ˇ}
25463    "});
25464    // test relative indent is preserved when tab
25465    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25466    cx.assert_editor_state(indoc! {"
25467        function main() {
25468                ˇfor item in $items; do
25469                    ˇwhile [ -n \"$item\" ]; do
25470                        ˇif [ \"$value\" -gt 10 ]; then
25471                            ˇcontinue
25472                        ˇelif [ \"$value\" -lt 0 ]; then
25473                            ˇbreak
25474                        ˇelse
25475                            ˇecho \"$item\"
25476                        ˇfi
25477                    ˇdone
25478                ˇdone
25479            ˇ}
25480    "});
25481
25482    // test cursor move to start of each line on tab
25483    // for `case` statement with patterns
25484    cx.set_state(indoc! {"
25485        function handle() {
25486        ˇ    case \"$1\" in
25487        ˇ        start)
25488        ˇ            echo \"a\"
25489        ˇ            ;;
25490        ˇ        stop)
25491        ˇ            echo \"b\"
25492        ˇ            ;;
25493        ˇ        *)
25494        ˇ            echo \"c\"
25495        ˇ            ;;
25496        ˇ    esac
25497        ˇ}
25498    "});
25499    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25500    cx.assert_editor_state(indoc! {"
25501        function handle() {
25502            ˇcase \"$1\" in
25503                ˇstart)
25504                    ˇecho \"a\"
25505                    ˇ;;
25506                ˇstop)
25507                    ˇecho \"b\"
25508                    ˇ;;
25509                ˇ*)
25510                    ˇecho \"c\"
25511                    ˇ;;
25512            ˇesac
25513        ˇ}
25514    "});
25515}
25516
25517#[gpui::test]
25518async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25519    init_test(cx, |_| {});
25520
25521    let mut cx = EditorTestContext::new(cx).await;
25522    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25523    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25524
25525    // test indents on comment insert
25526    cx.set_state(indoc! {"
25527        function main() {
25528        ˇ    for item in $items; do
25529        ˇ        while [ -n \"$item\" ]; do
25530        ˇ            if [ \"$value\" -gt 10 ]; then
25531        ˇ                continue
25532        ˇ            elif [ \"$value\" -lt 0 ]; then
25533        ˇ                break
25534        ˇ            else
25535        ˇ                echo \"$item\"
25536        ˇ            fi
25537        ˇ        done
25538        ˇ    done
25539        ˇ}
25540    "});
25541    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25542    cx.assert_editor_state(indoc! {"
25543        function main() {
25544        #ˇ    for item in $items; do
25545        #ˇ        while [ -n \"$item\" ]; do
25546        #ˇ            if [ \"$value\" -gt 10 ]; then
25547        #ˇ                continue
25548        #ˇ            elif [ \"$value\" -lt 0 ]; then
25549        #ˇ                break
25550        #ˇ            else
25551        #ˇ                echo \"$item\"
25552        #ˇ            fi
25553        #ˇ        done
25554        #ˇ    done
25555        #ˇ}
25556    "});
25557}
25558
25559#[gpui::test]
25560async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25561    init_test(cx, |_| {});
25562
25563    let mut cx = EditorTestContext::new(cx).await;
25564    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25565    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25566
25567    // test `else` auto outdents when typed inside `if` block
25568    cx.set_state(indoc! {"
25569        if [ \"$1\" = \"test\" ]; then
25570            echo \"foo bar\"
25571            ˇ
25572    "});
25573    cx.update_editor(|editor, window, cx| {
25574        editor.handle_input("else", window, cx);
25575    });
25576    cx.assert_editor_state(indoc! {"
25577        if [ \"$1\" = \"test\" ]; then
25578            echo \"foo bar\"
25579        elseˇ
25580    "});
25581
25582    // test `elif` auto outdents when typed inside `if` block
25583    cx.set_state(indoc! {"
25584        if [ \"$1\" = \"test\" ]; then
25585            echo \"foo bar\"
25586            ˇ
25587    "});
25588    cx.update_editor(|editor, window, cx| {
25589        editor.handle_input("elif", window, cx);
25590    });
25591    cx.assert_editor_state(indoc! {"
25592        if [ \"$1\" = \"test\" ]; then
25593            echo \"foo bar\"
25594        elifˇ
25595    "});
25596
25597    // test `fi` auto outdents when typed inside `else` block
25598    cx.set_state(indoc! {"
25599        if [ \"$1\" = \"test\" ]; then
25600            echo \"foo bar\"
25601        else
25602            echo \"bar baz\"
25603            ˇ
25604    "});
25605    cx.update_editor(|editor, window, cx| {
25606        editor.handle_input("fi", window, cx);
25607    });
25608    cx.assert_editor_state(indoc! {"
25609        if [ \"$1\" = \"test\" ]; then
25610            echo \"foo bar\"
25611        else
25612            echo \"bar baz\"
25613        fiˇ
25614    "});
25615
25616    // test `done` auto outdents when typed inside `while` block
25617    cx.set_state(indoc! {"
25618        while read line; do
25619            echo \"$line\"
25620            ˇ
25621    "});
25622    cx.update_editor(|editor, window, cx| {
25623        editor.handle_input("done", window, cx);
25624    });
25625    cx.assert_editor_state(indoc! {"
25626        while read line; do
25627            echo \"$line\"
25628        doneˇ
25629    "});
25630
25631    // test `done` auto outdents when typed inside `for` block
25632    cx.set_state(indoc! {"
25633        for file in *.txt; do
25634            cat \"$file\"
25635            ˇ
25636    "});
25637    cx.update_editor(|editor, window, cx| {
25638        editor.handle_input("done", window, cx);
25639    });
25640    cx.assert_editor_state(indoc! {"
25641        for file in *.txt; do
25642            cat \"$file\"
25643        doneˇ
25644    "});
25645
25646    // test `esac` auto outdents when typed inside `case` block
25647    cx.set_state(indoc! {"
25648        case \"$1\" in
25649            start)
25650                echo \"foo bar\"
25651                ;;
25652            stop)
25653                echo \"bar baz\"
25654                ;;
25655            ˇ
25656    "});
25657    cx.update_editor(|editor, window, cx| {
25658        editor.handle_input("esac", window, cx);
25659    });
25660    cx.assert_editor_state(indoc! {"
25661        case \"$1\" in
25662            start)
25663                echo \"foo bar\"
25664                ;;
25665            stop)
25666                echo \"bar baz\"
25667                ;;
25668        esacˇ
25669    "});
25670
25671    // test `*)` auto outdents when typed inside `case` block
25672    cx.set_state(indoc! {"
25673        case \"$1\" in
25674            start)
25675                echo \"foo bar\"
25676                ;;
25677                ˇ
25678    "});
25679    cx.update_editor(|editor, window, cx| {
25680        editor.handle_input("*)", window, cx);
25681    });
25682    cx.assert_editor_state(indoc! {"
25683        case \"$1\" in
25684            start)
25685                echo \"foo bar\"
25686                ;;
25687            *)ˇ
25688    "});
25689
25690    // test `fi` outdents to correct level with nested if blocks
25691    cx.set_state(indoc! {"
25692        if [ \"$1\" = \"test\" ]; then
25693            echo \"outer if\"
25694            if [ \"$2\" = \"debug\" ]; then
25695                echo \"inner if\"
25696                ˇ
25697    "});
25698    cx.update_editor(|editor, window, cx| {
25699        editor.handle_input("fi", window, cx);
25700    });
25701    cx.assert_editor_state(indoc! {"
25702        if [ \"$1\" = \"test\" ]; then
25703            echo \"outer if\"
25704            if [ \"$2\" = \"debug\" ]; then
25705                echo \"inner if\"
25706            fiˇ
25707    "});
25708}
25709
25710#[gpui::test]
25711async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25712    init_test(cx, |_| {});
25713    update_test_language_settings(cx, |settings| {
25714        settings.defaults.extend_comment_on_newline = Some(false);
25715    });
25716    let mut cx = EditorTestContext::new(cx).await;
25717    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25718    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25719
25720    // test correct indent after newline on comment
25721    cx.set_state(indoc! {"
25722        # COMMENT:ˇ
25723    "});
25724    cx.update_editor(|editor, window, cx| {
25725        editor.newline(&Newline, window, cx);
25726    });
25727    cx.assert_editor_state(indoc! {"
25728        # COMMENT:
25729        ˇ
25730    "});
25731
25732    // test correct indent after newline after `then`
25733    cx.set_state(indoc! {"
25734
25735        if [ \"$1\" = \"test\" ]; thenˇ
25736    "});
25737    cx.update_editor(|editor, window, cx| {
25738        editor.newline(&Newline, window, cx);
25739    });
25740    cx.run_until_parked();
25741    cx.assert_editor_state(indoc! {"
25742
25743        if [ \"$1\" = \"test\" ]; then
25744            ˇ
25745    "});
25746
25747    // test correct indent after newline after `else`
25748    cx.set_state(indoc! {"
25749        if [ \"$1\" = \"test\" ]; then
25750        elseˇ
25751    "});
25752    cx.update_editor(|editor, window, cx| {
25753        editor.newline(&Newline, window, cx);
25754    });
25755    cx.run_until_parked();
25756    cx.assert_editor_state(indoc! {"
25757        if [ \"$1\" = \"test\" ]; then
25758        else
25759            ˇ
25760    "});
25761
25762    // test correct indent after newline after `elif`
25763    cx.set_state(indoc! {"
25764        if [ \"$1\" = \"test\" ]; then
25765        elifˇ
25766    "});
25767    cx.update_editor(|editor, window, cx| {
25768        editor.newline(&Newline, window, cx);
25769    });
25770    cx.run_until_parked();
25771    cx.assert_editor_state(indoc! {"
25772        if [ \"$1\" = \"test\" ]; then
25773        elif
25774            ˇ
25775    "});
25776
25777    // test correct indent after newline after `do`
25778    cx.set_state(indoc! {"
25779        for file in *.txt; doˇ
25780    "});
25781    cx.update_editor(|editor, window, cx| {
25782        editor.newline(&Newline, window, cx);
25783    });
25784    cx.run_until_parked();
25785    cx.assert_editor_state(indoc! {"
25786        for file in *.txt; do
25787            ˇ
25788    "});
25789
25790    // test correct indent after newline after case pattern
25791    cx.set_state(indoc! {"
25792        case \"$1\" in
25793            start)ˇ
25794    "});
25795    cx.update_editor(|editor, window, cx| {
25796        editor.newline(&Newline, window, cx);
25797    });
25798    cx.run_until_parked();
25799    cx.assert_editor_state(indoc! {"
25800        case \"$1\" in
25801            start)
25802                ˇ
25803    "});
25804
25805    // test correct indent after newline after case pattern
25806    cx.set_state(indoc! {"
25807        case \"$1\" in
25808            start)
25809                ;;
25810            *)ˇ
25811    "});
25812    cx.update_editor(|editor, window, cx| {
25813        editor.newline(&Newline, window, cx);
25814    });
25815    cx.run_until_parked();
25816    cx.assert_editor_state(indoc! {"
25817        case \"$1\" in
25818            start)
25819                ;;
25820            *)
25821                ˇ
25822    "});
25823
25824    // test correct indent after newline after function opening brace
25825    cx.set_state(indoc! {"
25826        function test() {ˇ}
25827    "});
25828    cx.update_editor(|editor, window, cx| {
25829        editor.newline(&Newline, window, cx);
25830    });
25831    cx.run_until_parked();
25832    cx.assert_editor_state(indoc! {"
25833        function test() {
25834            ˇ
25835        }
25836    "});
25837
25838    // test no extra indent after semicolon on same line
25839    cx.set_state(indoc! {"
25840        echo \"test\"25841    "});
25842    cx.update_editor(|editor, window, cx| {
25843        editor.newline(&Newline, window, cx);
25844    });
25845    cx.run_until_parked();
25846    cx.assert_editor_state(indoc! {"
25847        echo \"test\";
25848        ˇ
25849    "});
25850}
25851
25852fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25853    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25854    point..point
25855}
25856
25857#[track_caller]
25858fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25859    let (text, ranges) = marked_text_ranges(marked_text, true);
25860    assert_eq!(editor.text(cx), text);
25861    assert_eq!(
25862        editor.selections.ranges(&editor.display_snapshot(cx)),
25863        ranges
25864            .iter()
25865            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
25866            .collect::<Vec<_>>(),
25867        "Assert selections are {}",
25868        marked_text
25869    );
25870}
25871
25872pub fn handle_signature_help_request(
25873    cx: &mut EditorLspTestContext,
25874    mocked_response: lsp::SignatureHelp,
25875) -> impl Future<Output = ()> + use<> {
25876    let mut request =
25877        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25878            let mocked_response = mocked_response.clone();
25879            async move { Ok(Some(mocked_response)) }
25880        });
25881
25882    async move {
25883        request.next().await;
25884    }
25885}
25886
25887#[track_caller]
25888pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25889    cx.update_editor(|editor, _, _| {
25890        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25891            let entries = menu.entries.borrow();
25892            let entries = entries
25893                .iter()
25894                .map(|entry| entry.string.as_str())
25895                .collect::<Vec<_>>();
25896            assert_eq!(entries, expected);
25897        } else {
25898            panic!("Expected completions menu");
25899        }
25900    });
25901}
25902
25903#[gpui::test]
25904async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
25905    init_test(cx, |_| {});
25906    let mut cx = EditorLspTestContext::new_rust(
25907        lsp::ServerCapabilities {
25908            completion_provider: Some(lsp::CompletionOptions {
25909                ..Default::default()
25910            }),
25911            ..Default::default()
25912        },
25913        cx,
25914    )
25915    .await;
25916    cx.lsp
25917        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25918            Ok(Some(lsp::CompletionResponse::Array(vec![
25919                lsp::CompletionItem {
25920                    label: "unsafe".into(),
25921                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25922                        range: lsp::Range {
25923                            start: lsp::Position {
25924                                line: 0,
25925                                character: 9,
25926                            },
25927                            end: lsp::Position {
25928                                line: 0,
25929                                character: 11,
25930                            },
25931                        },
25932                        new_text: "unsafe".to_string(),
25933                    })),
25934                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
25935                    ..Default::default()
25936                },
25937            ])))
25938        });
25939
25940    cx.update_editor(|editor, _, cx| {
25941        editor.project().unwrap().update(cx, |project, cx| {
25942            project.snippets().update(cx, |snippets, _cx| {
25943                snippets.add_snippet_for_test(
25944                    None,
25945                    PathBuf::from("test_snippets.json"),
25946                    vec![
25947                        Arc::new(project::snippet_provider::Snippet {
25948                            prefix: vec![
25949                                "unlimited word count".to_string(),
25950                                "unlimit word count".to_string(),
25951                                "unlimited unknown".to_string(),
25952                            ],
25953                            body: "this is many words".to_string(),
25954                            description: Some("description".to_string()),
25955                            name: "multi-word snippet test".to_string(),
25956                        }),
25957                        Arc::new(project::snippet_provider::Snippet {
25958                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
25959                            body: "fewer words".to_string(),
25960                            description: Some("alt description".to_string()),
25961                            name: "other name".to_string(),
25962                        }),
25963                        Arc::new(project::snippet_provider::Snippet {
25964                            prefix: vec!["ab aa".to_string()],
25965                            body: "abcd".to_string(),
25966                            description: None,
25967                            name: "alphabet".to_string(),
25968                        }),
25969                    ],
25970                );
25971            });
25972        })
25973    });
25974
25975    let get_completions = |cx: &mut EditorLspTestContext| {
25976        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
25977            Some(CodeContextMenu::Completions(context_menu)) => {
25978                let entries = context_menu.entries.borrow();
25979                entries
25980                    .iter()
25981                    .map(|entry| entry.string.clone())
25982                    .collect_vec()
25983            }
25984            _ => vec![],
25985        })
25986    };
25987
25988    // snippets:
25989    //  @foo
25990    //  foo bar
25991    //
25992    // when typing:
25993    //
25994    // when typing:
25995    //  - if I type a symbol "open the completions with snippets only"
25996    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
25997    //
25998    // stuff we need:
25999    //  - filtering logic change?
26000    //  - remember how far back the completion started.
26001
26002    let test_cases: &[(&str, &[&str])] = &[
26003        (
26004            "un",
26005            &[
26006                "unsafe",
26007                "unlimit word count",
26008                "unlimited unknown",
26009                "unlimited word count",
26010                "unsnip",
26011            ],
26012        ),
26013        (
26014            "u ",
26015            &[
26016                "unlimit word count",
26017                "unlimited unknown",
26018                "unlimited word count",
26019            ],
26020        ),
26021        ("u a", &["ab aa", "unsafe"]), // unsAfe
26022        (
26023            "u u",
26024            &[
26025                "unsafe",
26026                "unlimit word count",
26027                "unlimited unknown", // ranked highest among snippets
26028                "unlimited word count",
26029                "unsnip",
26030            ],
26031        ),
26032        ("uw c", &["unlimit word count", "unlimited word count"]),
26033        (
26034            "u w",
26035            &[
26036                "unlimit word count",
26037                "unlimited word count",
26038                "unlimited unknown",
26039            ],
26040        ),
26041        ("u w ", &["unlimit word count", "unlimited word count"]),
26042        (
26043            "u ",
26044            &[
26045                "unlimit word count",
26046                "unlimited unknown",
26047                "unlimited word count",
26048            ],
26049        ),
26050        ("wor", &[]),
26051        ("uf", &["unsafe"]),
26052        ("af", &["unsafe"]),
26053        ("afu", &[]),
26054        (
26055            "ue",
26056            &["unsafe", "unlimited unknown", "unlimited word count"],
26057        ),
26058        ("@", &["@few"]),
26059        ("@few", &["@few"]),
26060        ("@ ", &[]),
26061        ("a@", &["@few"]),
26062        ("a@f", &["@few", "unsafe"]),
26063        ("a@fw", &["@few"]),
26064        ("a", &["ab aa", "unsafe"]),
26065        ("aa", &["ab aa"]),
26066        ("aaa", &["ab aa"]),
26067        ("ab", &["ab aa"]),
26068        ("ab ", &["ab aa"]),
26069        ("ab a", &["ab aa", "unsafe"]),
26070        ("ab ab", &["ab aa"]),
26071        ("ab ab aa", &["ab aa"]),
26072    ];
26073
26074    for &(input_to_simulate, expected_completions) in test_cases {
26075        cx.set_state("fn a() { ˇ }\n");
26076        for c in input_to_simulate.split("") {
26077            cx.simulate_input(c);
26078            cx.run_until_parked();
26079        }
26080        let expected_completions = expected_completions
26081            .iter()
26082            .map(|s| s.to_string())
26083            .collect_vec();
26084        assert_eq!(
26085            get_completions(&mut cx),
26086            expected_completions,
26087            "< actual / expected >, input = {input_to_simulate:?}",
26088        );
26089    }
26090}
26091
26092/// Handle completion request passing a marked string specifying where the completion
26093/// should be triggered from using '|' character, what range should be replaced, and what completions
26094/// should be returned using '<' and '>' to delimit the range.
26095///
26096/// Also see `handle_completion_request_with_insert_and_replace`.
26097#[track_caller]
26098pub fn handle_completion_request(
26099    marked_string: &str,
26100    completions: Vec<&'static str>,
26101    is_incomplete: bool,
26102    counter: Arc<AtomicUsize>,
26103    cx: &mut EditorLspTestContext,
26104) -> impl Future<Output = ()> {
26105    let complete_from_marker: TextRangeMarker = '|'.into();
26106    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26107    let (_, mut marked_ranges) = marked_text_ranges_by(
26108        marked_string,
26109        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26110    );
26111
26112    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26113        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26114    ));
26115    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26116    let replace_range =
26117        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26118
26119    let mut request =
26120        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26121            let completions = completions.clone();
26122            counter.fetch_add(1, atomic::Ordering::Release);
26123            async move {
26124                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26125                assert_eq!(
26126                    params.text_document_position.position,
26127                    complete_from_position
26128                );
26129                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26130                    is_incomplete,
26131                    item_defaults: None,
26132                    items: completions
26133                        .iter()
26134                        .map(|completion_text| lsp::CompletionItem {
26135                            label: completion_text.to_string(),
26136                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26137                                range: replace_range,
26138                                new_text: completion_text.to_string(),
26139                            })),
26140                            ..Default::default()
26141                        })
26142                        .collect(),
26143                })))
26144            }
26145        });
26146
26147    async move {
26148        request.next().await;
26149    }
26150}
26151
26152/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26153/// given instead, which also contains an `insert` range.
26154///
26155/// This function uses markers to define ranges:
26156/// - `|` marks the cursor position
26157/// - `<>` marks the replace range
26158/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26159pub fn handle_completion_request_with_insert_and_replace(
26160    cx: &mut EditorLspTestContext,
26161    marked_string: &str,
26162    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26163    counter: Arc<AtomicUsize>,
26164) -> impl Future<Output = ()> {
26165    let complete_from_marker: TextRangeMarker = '|'.into();
26166    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26167    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26168
26169    let (_, mut marked_ranges) = marked_text_ranges_by(
26170        marked_string,
26171        vec![
26172            complete_from_marker.clone(),
26173            replace_range_marker.clone(),
26174            insert_range_marker.clone(),
26175        ],
26176    );
26177
26178    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26179        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26180    ));
26181    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26182    let replace_range =
26183        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26184
26185    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26186        Some(ranges) if !ranges.is_empty() => {
26187            let range1 = ranges[0].clone();
26188            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26189        }
26190        _ => lsp::Range {
26191            start: replace_range.start,
26192            end: complete_from_position,
26193        },
26194    };
26195
26196    let mut request =
26197        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26198            let completions = completions.clone();
26199            counter.fetch_add(1, atomic::Ordering::Release);
26200            async move {
26201                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26202                assert_eq!(
26203                    params.text_document_position.position, complete_from_position,
26204                    "marker `|` position doesn't match",
26205                );
26206                Ok(Some(lsp::CompletionResponse::Array(
26207                    completions
26208                        .iter()
26209                        .map(|(label, new_text)| lsp::CompletionItem {
26210                            label: label.to_string(),
26211                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26212                                lsp::InsertReplaceEdit {
26213                                    insert: insert_range,
26214                                    replace: replace_range,
26215                                    new_text: new_text.to_string(),
26216                                },
26217                            )),
26218                            ..Default::default()
26219                        })
26220                        .collect(),
26221                )))
26222            }
26223        });
26224
26225    async move {
26226        request.next().await;
26227    }
26228}
26229
26230fn handle_resolve_completion_request(
26231    cx: &mut EditorLspTestContext,
26232    edits: Option<Vec<(&'static str, &'static str)>>,
26233) -> impl Future<Output = ()> {
26234    let edits = edits.map(|edits| {
26235        edits
26236            .iter()
26237            .map(|(marked_string, new_text)| {
26238                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26239                let replace_range = cx.to_lsp_range(
26240                    MultiBufferOffset(marked_ranges[0].start)
26241                        ..MultiBufferOffset(marked_ranges[0].end),
26242                );
26243                lsp::TextEdit::new(replace_range, new_text.to_string())
26244            })
26245            .collect::<Vec<_>>()
26246    });
26247
26248    let mut request =
26249        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26250            let edits = edits.clone();
26251            async move {
26252                Ok(lsp::CompletionItem {
26253                    additional_text_edits: edits,
26254                    ..Default::default()
26255                })
26256            }
26257        });
26258
26259    async move {
26260        request.next().await;
26261    }
26262}
26263
26264pub(crate) fn update_test_language_settings(
26265    cx: &mut TestAppContext,
26266    f: impl Fn(&mut AllLanguageSettingsContent),
26267) {
26268    cx.update(|cx| {
26269        SettingsStore::update_global(cx, |store, cx| {
26270            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26271        });
26272    });
26273}
26274
26275pub(crate) fn update_test_project_settings(
26276    cx: &mut TestAppContext,
26277    f: impl Fn(&mut ProjectSettingsContent),
26278) {
26279    cx.update(|cx| {
26280        SettingsStore::update_global(cx, |store, cx| {
26281            store.update_user_settings(cx, |settings| f(&mut settings.project));
26282        });
26283    });
26284}
26285
26286pub(crate) fn update_test_editor_settings(
26287    cx: &mut TestAppContext,
26288    f: impl Fn(&mut EditorSettingsContent),
26289) {
26290    cx.update(|cx| {
26291        SettingsStore::update_global(cx, |store, cx| {
26292            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26293        })
26294    })
26295}
26296
26297pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26298    cx.update(|cx| {
26299        assets::Assets.load_test_fonts(cx);
26300        let store = SettingsStore::test(cx);
26301        cx.set_global(store);
26302        theme::init(theme::LoadThemes::JustBase, cx);
26303        release_channel::init(semver::Version::new(0, 0, 0), cx);
26304        crate::init(cx);
26305    });
26306    zlog::init_test();
26307    update_test_language_settings(cx, f);
26308}
26309
26310#[track_caller]
26311fn assert_hunk_revert(
26312    not_reverted_text_with_selections: &str,
26313    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
26314    expected_reverted_text_with_selections: &str,
26315    base_text: &str,
26316    cx: &mut EditorLspTestContext,
26317) {
26318    cx.set_state(not_reverted_text_with_selections);
26319    cx.set_head_text(base_text);
26320    cx.executor().run_until_parked();
26321
26322    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
26323        let snapshot = editor.snapshot(window, cx);
26324        let reverted_hunk_statuses = snapshot
26325            .buffer_snapshot()
26326            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
26327            .map(|hunk| hunk.status().kind)
26328            .collect::<Vec<_>>();
26329
26330        editor.git_restore(&Default::default(), window, cx);
26331        reverted_hunk_statuses
26332    });
26333    cx.executor().run_until_parked();
26334    cx.assert_editor_state(expected_reverted_text_with_selections);
26335    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
26336}
26337
26338#[gpui::test(iterations = 10)]
26339async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
26340    init_test(cx, |_| {});
26341
26342    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
26343    let counter = diagnostic_requests.clone();
26344
26345    let fs = FakeFs::new(cx.executor());
26346    fs.insert_tree(
26347        path!("/a"),
26348        json!({
26349            "first.rs": "fn main() { let a = 5; }",
26350            "second.rs": "// Test file",
26351        }),
26352    )
26353    .await;
26354
26355    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26356    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26357    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26358
26359    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26360    language_registry.add(rust_lang());
26361    let mut fake_servers = language_registry.register_fake_lsp(
26362        "Rust",
26363        FakeLspAdapter {
26364            capabilities: lsp::ServerCapabilities {
26365                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
26366                    lsp::DiagnosticOptions {
26367                        identifier: None,
26368                        inter_file_dependencies: true,
26369                        workspace_diagnostics: true,
26370                        work_done_progress_options: Default::default(),
26371                    },
26372                )),
26373                ..Default::default()
26374            },
26375            ..Default::default()
26376        },
26377    );
26378
26379    let editor = workspace
26380        .update(cx, |workspace, window, cx| {
26381            workspace.open_abs_path(
26382                PathBuf::from(path!("/a/first.rs")),
26383                OpenOptions::default(),
26384                window,
26385                cx,
26386            )
26387        })
26388        .unwrap()
26389        .await
26390        .unwrap()
26391        .downcast::<Editor>()
26392        .unwrap();
26393    let fake_server = fake_servers.next().await.unwrap();
26394    let server_id = fake_server.server.server_id();
26395    let mut first_request = fake_server
26396        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
26397            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
26398            let result_id = Some(new_result_id.to_string());
26399            assert_eq!(
26400                params.text_document.uri,
26401                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26402            );
26403            async move {
26404                Ok(lsp::DocumentDiagnosticReportResult::Report(
26405                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
26406                        related_documents: None,
26407                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
26408                            items: Vec::new(),
26409                            result_id,
26410                        },
26411                    }),
26412                ))
26413            }
26414        });
26415
26416    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
26417        project.update(cx, |project, cx| {
26418            let buffer_id = editor
26419                .read(cx)
26420                .buffer()
26421                .read(cx)
26422                .as_singleton()
26423                .expect("created a singleton buffer")
26424                .read(cx)
26425                .remote_id();
26426            let buffer_result_id = project
26427                .lsp_store()
26428                .read(cx)
26429                .result_id(server_id, buffer_id, cx);
26430            assert_eq!(expected, buffer_result_id);
26431        });
26432    };
26433
26434    ensure_result_id(None, cx);
26435    cx.executor().advance_clock(Duration::from_millis(60));
26436    cx.executor().run_until_parked();
26437    assert_eq!(
26438        diagnostic_requests.load(atomic::Ordering::Acquire),
26439        1,
26440        "Opening file should trigger diagnostic request"
26441    );
26442    first_request
26443        .next()
26444        .await
26445        .expect("should have sent the first diagnostics pull request");
26446    ensure_result_id(Some("1".to_string()), cx);
26447
26448    // Editing should trigger diagnostics
26449    editor.update_in(cx, |editor, window, cx| {
26450        editor.handle_input("2", window, cx)
26451    });
26452    cx.executor().advance_clock(Duration::from_millis(60));
26453    cx.executor().run_until_parked();
26454    assert_eq!(
26455        diagnostic_requests.load(atomic::Ordering::Acquire),
26456        2,
26457        "Editing should trigger diagnostic request"
26458    );
26459    ensure_result_id(Some("2".to_string()), cx);
26460
26461    // Moving cursor should not trigger diagnostic request
26462    editor.update_in(cx, |editor, window, cx| {
26463        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26464            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
26465        });
26466    });
26467    cx.executor().advance_clock(Duration::from_millis(60));
26468    cx.executor().run_until_parked();
26469    assert_eq!(
26470        diagnostic_requests.load(atomic::Ordering::Acquire),
26471        2,
26472        "Cursor movement should not trigger diagnostic request"
26473    );
26474    ensure_result_id(Some("2".to_string()), cx);
26475    // Multiple rapid edits should be debounced
26476    for _ in 0..5 {
26477        editor.update_in(cx, |editor, window, cx| {
26478            editor.handle_input("x", window, cx)
26479        });
26480    }
26481    cx.executor().advance_clock(Duration::from_millis(60));
26482    cx.executor().run_until_parked();
26483
26484    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
26485    assert!(
26486        final_requests <= 4,
26487        "Multiple rapid edits should be debounced (got {final_requests} requests)",
26488    );
26489    ensure_result_id(Some(final_requests.to_string()), cx);
26490}
26491
26492#[gpui::test]
26493async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
26494    // Regression test for issue #11671
26495    // Previously, adding a cursor after moving multiple cursors would reset
26496    // the cursor count instead of adding to the existing cursors.
26497    init_test(cx, |_| {});
26498    let mut cx = EditorTestContext::new(cx).await;
26499
26500    // Create a simple buffer with cursor at start
26501    cx.set_state(indoc! {"
26502        ˇaaaa
26503        bbbb
26504        cccc
26505        dddd
26506        eeee
26507        ffff
26508        gggg
26509        hhhh"});
26510
26511    // Add 2 cursors below (so we have 3 total)
26512    cx.update_editor(|editor, window, cx| {
26513        editor.add_selection_below(&Default::default(), window, cx);
26514        editor.add_selection_below(&Default::default(), window, cx);
26515    });
26516
26517    // Verify we have 3 cursors
26518    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
26519    assert_eq!(
26520        initial_count, 3,
26521        "Should have 3 cursors after adding 2 below"
26522    );
26523
26524    // Move down one line
26525    cx.update_editor(|editor, window, cx| {
26526        editor.move_down(&MoveDown, window, cx);
26527    });
26528
26529    // Add another cursor below
26530    cx.update_editor(|editor, window, cx| {
26531        editor.add_selection_below(&Default::default(), window, cx);
26532    });
26533
26534    // Should now have 4 cursors (3 original + 1 new)
26535    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
26536    assert_eq!(
26537        final_count, 4,
26538        "Should have 4 cursors after moving and adding another"
26539    );
26540}
26541
26542#[gpui::test]
26543async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
26544    init_test(cx, |_| {});
26545
26546    let mut cx = EditorTestContext::new(cx).await;
26547
26548    cx.set_state(indoc!(
26549        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
26550           Second line here"#
26551    ));
26552
26553    cx.update_editor(|editor, window, cx| {
26554        // Enable soft wrapping with a narrow width to force soft wrapping and
26555        // confirm that more than 2 rows are being displayed.
26556        editor.set_wrap_width(Some(100.0.into()), cx);
26557        assert!(editor.display_text(cx).lines().count() > 2);
26558
26559        editor.add_selection_below(
26560            &AddSelectionBelow {
26561                skip_soft_wrap: true,
26562            },
26563            window,
26564            cx,
26565        );
26566
26567        assert_eq!(
26568            display_ranges(editor, cx),
26569            &[
26570                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26571                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
26572            ]
26573        );
26574
26575        editor.add_selection_above(
26576            &AddSelectionAbove {
26577                skip_soft_wrap: true,
26578            },
26579            window,
26580            cx,
26581        );
26582
26583        assert_eq!(
26584            display_ranges(editor, cx),
26585            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26586        );
26587
26588        editor.add_selection_below(
26589            &AddSelectionBelow {
26590                skip_soft_wrap: false,
26591            },
26592            window,
26593            cx,
26594        );
26595
26596        assert_eq!(
26597            display_ranges(editor, cx),
26598            &[
26599                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26600                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26601            ]
26602        );
26603
26604        editor.add_selection_above(
26605            &AddSelectionAbove {
26606                skip_soft_wrap: false,
26607            },
26608            window,
26609            cx,
26610        );
26611
26612        assert_eq!(
26613            display_ranges(editor, cx),
26614            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26615        );
26616    });
26617}
26618
26619#[gpui::test(iterations = 10)]
26620async fn test_document_colors(cx: &mut TestAppContext) {
26621    let expected_color = Rgba {
26622        r: 0.33,
26623        g: 0.33,
26624        b: 0.33,
26625        a: 0.33,
26626    };
26627
26628    init_test(cx, |_| {});
26629
26630    let fs = FakeFs::new(cx.executor());
26631    fs.insert_tree(
26632        path!("/a"),
26633        json!({
26634            "first.rs": "fn main() { let a = 5; }",
26635        }),
26636    )
26637    .await;
26638
26639    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26640    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26641    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26642
26643    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26644    language_registry.add(rust_lang());
26645    let mut fake_servers = language_registry.register_fake_lsp(
26646        "Rust",
26647        FakeLspAdapter {
26648            capabilities: lsp::ServerCapabilities {
26649                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26650                ..lsp::ServerCapabilities::default()
26651            },
26652            name: "rust-analyzer",
26653            ..FakeLspAdapter::default()
26654        },
26655    );
26656    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26657        "Rust",
26658        FakeLspAdapter {
26659            capabilities: lsp::ServerCapabilities {
26660                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26661                ..lsp::ServerCapabilities::default()
26662            },
26663            name: "not-rust-analyzer",
26664            ..FakeLspAdapter::default()
26665        },
26666    );
26667
26668    let editor = workspace
26669        .update(cx, |workspace, window, cx| {
26670            workspace.open_abs_path(
26671                PathBuf::from(path!("/a/first.rs")),
26672                OpenOptions::default(),
26673                window,
26674                cx,
26675            )
26676        })
26677        .unwrap()
26678        .await
26679        .unwrap()
26680        .downcast::<Editor>()
26681        .unwrap();
26682    let fake_language_server = fake_servers.next().await.unwrap();
26683    let fake_language_server_without_capabilities =
26684        fake_servers_without_capabilities.next().await.unwrap();
26685    let requests_made = Arc::new(AtomicUsize::new(0));
26686    let closure_requests_made = Arc::clone(&requests_made);
26687    let mut color_request_handle = fake_language_server
26688        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26689            let requests_made = Arc::clone(&closure_requests_made);
26690            async move {
26691                assert_eq!(
26692                    params.text_document.uri,
26693                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26694                );
26695                requests_made.fetch_add(1, atomic::Ordering::Release);
26696                Ok(vec![
26697                    lsp::ColorInformation {
26698                        range: lsp::Range {
26699                            start: lsp::Position {
26700                                line: 0,
26701                                character: 0,
26702                            },
26703                            end: lsp::Position {
26704                                line: 0,
26705                                character: 1,
26706                            },
26707                        },
26708                        color: lsp::Color {
26709                            red: 0.33,
26710                            green: 0.33,
26711                            blue: 0.33,
26712                            alpha: 0.33,
26713                        },
26714                    },
26715                    lsp::ColorInformation {
26716                        range: lsp::Range {
26717                            start: lsp::Position {
26718                                line: 0,
26719                                character: 0,
26720                            },
26721                            end: lsp::Position {
26722                                line: 0,
26723                                character: 1,
26724                            },
26725                        },
26726                        color: lsp::Color {
26727                            red: 0.33,
26728                            green: 0.33,
26729                            blue: 0.33,
26730                            alpha: 0.33,
26731                        },
26732                    },
26733                ])
26734            }
26735        });
26736
26737    let _handle = fake_language_server_without_capabilities
26738        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26739            panic!("Should not be called");
26740        });
26741    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26742    color_request_handle.next().await.unwrap();
26743    cx.run_until_parked();
26744    assert_eq!(
26745        1,
26746        requests_made.load(atomic::Ordering::Acquire),
26747        "Should query for colors once per editor open"
26748    );
26749    editor.update_in(cx, |editor, _, cx| {
26750        assert_eq!(
26751            vec![expected_color],
26752            extract_color_inlays(editor, cx),
26753            "Should have an initial inlay"
26754        );
26755    });
26756
26757    // opening another file in a split should not influence the LSP query counter
26758    workspace
26759        .update(cx, |workspace, window, cx| {
26760            assert_eq!(
26761                workspace.panes().len(),
26762                1,
26763                "Should have one pane with one editor"
26764            );
26765            workspace.move_item_to_pane_in_direction(
26766                &MoveItemToPaneInDirection {
26767                    direction: SplitDirection::Right,
26768                    focus: false,
26769                    clone: true,
26770                },
26771                window,
26772                cx,
26773            );
26774        })
26775        .unwrap();
26776    cx.run_until_parked();
26777    workspace
26778        .update(cx, |workspace, _, cx| {
26779            let panes = workspace.panes();
26780            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26781            for pane in panes {
26782                let editor = pane
26783                    .read(cx)
26784                    .active_item()
26785                    .and_then(|item| item.downcast::<Editor>())
26786                    .expect("Should have opened an editor in each split");
26787                let editor_file = editor
26788                    .read(cx)
26789                    .buffer()
26790                    .read(cx)
26791                    .as_singleton()
26792                    .expect("test deals with singleton buffers")
26793                    .read(cx)
26794                    .file()
26795                    .expect("test buffese should have a file")
26796                    .path();
26797                assert_eq!(
26798                    editor_file.as_ref(),
26799                    rel_path("first.rs"),
26800                    "Both editors should be opened for the same file"
26801                )
26802            }
26803        })
26804        .unwrap();
26805
26806    cx.executor().advance_clock(Duration::from_millis(500));
26807    let save = editor.update_in(cx, |editor, window, cx| {
26808        editor.move_to_end(&MoveToEnd, window, cx);
26809        editor.handle_input("dirty", window, cx);
26810        editor.save(
26811            SaveOptions {
26812                format: true,
26813                autosave: true,
26814            },
26815            project.clone(),
26816            window,
26817            cx,
26818        )
26819    });
26820    save.await.unwrap();
26821
26822    color_request_handle.next().await.unwrap();
26823    cx.run_until_parked();
26824    assert_eq!(
26825        2,
26826        requests_made.load(atomic::Ordering::Acquire),
26827        "Should query for colors once per save (deduplicated) and once per formatting after save"
26828    );
26829
26830    drop(editor);
26831    let close = workspace
26832        .update(cx, |workspace, window, cx| {
26833            workspace.active_pane().update(cx, |pane, cx| {
26834                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26835            })
26836        })
26837        .unwrap();
26838    close.await.unwrap();
26839    let close = workspace
26840        .update(cx, |workspace, window, cx| {
26841            workspace.active_pane().update(cx, |pane, cx| {
26842                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26843            })
26844        })
26845        .unwrap();
26846    close.await.unwrap();
26847    assert_eq!(
26848        2,
26849        requests_made.load(atomic::Ordering::Acquire),
26850        "After saving and closing all editors, no extra requests should be made"
26851    );
26852    workspace
26853        .update(cx, |workspace, _, cx| {
26854            assert!(
26855                workspace.active_item(cx).is_none(),
26856                "Should close all editors"
26857            )
26858        })
26859        .unwrap();
26860
26861    workspace
26862        .update(cx, |workspace, window, cx| {
26863            workspace.active_pane().update(cx, |pane, cx| {
26864                pane.navigate_backward(&workspace::GoBack, window, cx);
26865            })
26866        })
26867        .unwrap();
26868    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26869    cx.run_until_parked();
26870    let editor = workspace
26871        .update(cx, |workspace, _, cx| {
26872            workspace
26873                .active_item(cx)
26874                .expect("Should have reopened the editor again after navigating back")
26875                .downcast::<Editor>()
26876                .expect("Should be an editor")
26877        })
26878        .unwrap();
26879
26880    assert_eq!(
26881        2,
26882        requests_made.load(atomic::Ordering::Acquire),
26883        "Cache should be reused on buffer close and reopen"
26884    );
26885    editor.update(cx, |editor, cx| {
26886        assert_eq!(
26887            vec![expected_color],
26888            extract_color_inlays(editor, cx),
26889            "Should have an initial inlay"
26890        );
26891    });
26892
26893    drop(color_request_handle);
26894    let closure_requests_made = Arc::clone(&requests_made);
26895    let mut empty_color_request_handle = fake_language_server
26896        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26897            let requests_made = Arc::clone(&closure_requests_made);
26898            async move {
26899                assert_eq!(
26900                    params.text_document.uri,
26901                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26902                );
26903                requests_made.fetch_add(1, atomic::Ordering::Release);
26904                Ok(Vec::new())
26905            }
26906        });
26907    let save = editor.update_in(cx, |editor, window, cx| {
26908        editor.move_to_end(&MoveToEnd, window, cx);
26909        editor.handle_input("dirty_again", window, cx);
26910        editor.save(
26911            SaveOptions {
26912                format: false,
26913                autosave: true,
26914            },
26915            project.clone(),
26916            window,
26917            cx,
26918        )
26919    });
26920    save.await.unwrap();
26921
26922    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26923    empty_color_request_handle.next().await.unwrap();
26924    cx.run_until_parked();
26925    assert_eq!(
26926        3,
26927        requests_made.load(atomic::Ordering::Acquire),
26928        "Should query for colors once per save only, as formatting was not requested"
26929    );
26930    editor.update(cx, |editor, cx| {
26931        assert_eq!(
26932            Vec::<Rgba>::new(),
26933            extract_color_inlays(editor, cx),
26934            "Should clear all colors when the server returns an empty response"
26935        );
26936    });
26937}
26938
26939#[gpui::test]
26940async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26941    init_test(cx, |_| {});
26942    let (editor, cx) = cx.add_window_view(Editor::single_line);
26943    editor.update_in(cx, |editor, window, cx| {
26944        editor.set_text("oops\n\nwow\n", window, cx)
26945    });
26946    cx.run_until_parked();
26947    editor.update(cx, |editor, cx| {
26948        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26949    });
26950    editor.update(cx, |editor, cx| {
26951        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
26952    });
26953    cx.run_until_parked();
26954    editor.update(cx, |editor, cx| {
26955        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26956    });
26957}
26958
26959#[gpui::test]
26960async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26961    init_test(cx, |_| {});
26962
26963    cx.update(|cx| {
26964        register_project_item::<Editor>(cx);
26965    });
26966
26967    let fs = FakeFs::new(cx.executor());
26968    fs.insert_tree("/root1", json!({})).await;
26969    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26970        .await;
26971
26972    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26973    let (workspace, cx) =
26974        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26975
26976    let worktree_id = project.update(cx, |project, cx| {
26977        project.worktrees(cx).next().unwrap().read(cx).id()
26978    });
26979
26980    let handle = workspace
26981        .update_in(cx, |workspace, window, cx| {
26982            let project_path = (worktree_id, rel_path("one.pdf"));
26983            workspace.open_path(project_path, None, true, window, cx)
26984        })
26985        .await
26986        .unwrap();
26987
26988    assert_eq!(
26989        handle.to_any_view().entity_type(),
26990        TypeId::of::<InvalidItemView>()
26991    );
26992}
26993
26994#[gpui::test]
26995async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26996    init_test(cx, |_| {});
26997
26998    let language = Arc::new(Language::new(
26999        LanguageConfig::default(),
27000        Some(tree_sitter_rust::LANGUAGE.into()),
27001    ));
27002
27003    // Test hierarchical sibling navigation
27004    let text = r#"
27005        fn outer() {
27006            if condition {
27007                let a = 1;
27008            }
27009            let b = 2;
27010        }
27011
27012        fn another() {
27013            let c = 3;
27014        }
27015    "#;
27016
27017    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27018    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27019    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27020
27021    // Wait for parsing to complete
27022    editor
27023        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27024        .await;
27025
27026    editor.update_in(cx, |editor, window, cx| {
27027        // Start by selecting "let a = 1;" inside the if block
27028        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27029            s.select_display_ranges([
27030                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27031            ]);
27032        });
27033
27034        let initial_selection = editor
27035            .selections
27036            .display_ranges(&editor.display_snapshot(cx));
27037        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27038
27039        // Test select next sibling - should move up levels to find the next sibling
27040        // Since "let a = 1;" has no siblings in the if block, it should move up
27041        // to find "let b = 2;" which is a sibling of the if block
27042        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27043        let next_selection = editor
27044            .selections
27045            .display_ranges(&editor.display_snapshot(cx));
27046
27047        // Should have a selection and it should be different from the initial
27048        assert_eq!(
27049            next_selection.len(),
27050            1,
27051            "Should have one selection after next"
27052        );
27053        assert_ne!(
27054            next_selection[0], initial_selection[0],
27055            "Next sibling selection should be different"
27056        );
27057
27058        // Test hierarchical navigation by going to the end of the current function
27059        // and trying to navigate to the next function
27060        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27061            s.select_display_ranges([
27062                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27063            ]);
27064        });
27065
27066        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27067        let function_next_selection = editor
27068            .selections
27069            .display_ranges(&editor.display_snapshot(cx));
27070
27071        // Should move to the next function
27072        assert_eq!(
27073            function_next_selection.len(),
27074            1,
27075            "Should have one selection after function next"
27076        );
27077
27078        // Test select previous sibling navigation
27079        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27080        let prev_selection = editor
27081            .selections
27082            .display_ranges(&editor.display_snapshot(cx));
27083
27084        // Should have a selection and it should be different
27085        assert_eq!(
27086            prev_selection.len(),
27087            1,
27088            "Should have one selection after prev"
27089        );
27090        assert_ne!(
27091            prev_selection[0], function_next_selection[0],
27092            "Previous sibling selection should be different from next"
27093        );
27094    });
27095}
27096
27097#[gpui::test]
27098async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27099    init_test(cx, |_| {});
27100
27101    let mut cx = EditorTestContext::new(cx).await;
27102    cx.set_state(
27103        "let ˇvariable = 42;
27104let another = variable + 1;
27105let result = variable * 2;",
27106    );
27107
27108    // Set up document highlights manually (simulating LSP response)
27109    cx.update_editor(|editor, _window, cx| {
27110        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27111
27112        // Create highlights for "variable" occurrences
27113        let highlight_ranges = [
27114            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27115            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27116            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27117        ];
27118
27119        let anchor_ranges: Vec<_> = highlight_ranges
27120            .iter()
27121            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27122            .collect();
27123
27124        editor.highlight_background::<DocumentHighlightRead>(
27125            &anchor_ranges,
27126            |theme| theme.colors().editor_document_highlight_read_background,
27127            cx,
27128        );
27129    });
27130
27131    // Go to next highlight - should move to second "variable"
27132    cx.update_editor(|editor, window, cx| {
27133        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27134    });
27135    cx.assert_editor_state(
27136        "let variable = 42;
27137let another = ˇvariable + 1;
27138let result = variable * 2;",
27139    );
27140
27141    // Go to next highlight - should move to third "variable"
27142    cx.update_editor(|editor, window, cx| {
27143        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27144    });
27145    cx.assert_editor_state(
27146        "let variable = 42;
27147let another = variable + 1;
27148let result = ˇvariable * 2;",
27149    );
27150
27151    // Go to next highlight - should stay at third "variable" (no wrap-around)
27152    cx.update_editor(|editor, window, cx| {
27153        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27154    });
27155    cx.assert_editor_state(
27156        "let variable = 42;
27157let another = variable + 1;
27158let result = ˇvariable * 2;",
27159    );
27160
27161    // Now test going backwards from third position
27162    cx.update_editor(|editor, window, cx| {
27163        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27164    });
27165    cx.assert_editor_state(
27166        "let variable = 42;
27167let another = ˇvariable + 1;
27168let result = variable * 2;",
27169    );
27170
27171    // Go to previous highlight - should move to first "variable"
27172    cx.update_editor(|editor, window, cx| {
27173        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27174    });
27175    cx.assert_editor_state(
27176        "let ˇvariable = 42;
27177let another = variable + 1;
27178let result = variable * 2;",
27179    );
27180
27181    // Go to previous highlight - should stay on first "variable"
27182    cx.update_editor(|editor, window, cx| {
27183        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27184    });
27185    cx.assert_editor_state(
27186        "let ˇvariable = 42;
27187let another = variable + 1;
27188let result = variable * 2;",
27189    );
27190}
27191
27192#[gpui::test]
27193async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
27194    cx: &mut gpui::TestAppContext,
27195) {
27196    init_test(cx, |_| {});
27197
27198    let url = "https://zed.dev";
27199
27200    let markdown_language = Arc::new(Language::new(
27201        LanguageConfig {
27202            name: "Markdown".into(),
27203            ..LanguageConfig::default()
27204        },
27205        None,
27206    ));
27207
27208    let mut cx = EditorTestContext::new(cx).await;
27209    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27210    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27211
27212    cx.update_editor(|editor, window, cx| {
27213        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27214        editor.paste(&Paste, window, cx);
27215    });
27216
27217    cx.assert_editor_state(&format!(
27218        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27219    ));
27220}
27221
27222#[gpui::test]
27223async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
27224    cx: &mut gpui::TestAppContext,
27225) {
27226    init_test(cx, |_| {});
27227
27228    let url = "https://zed.dev";
27229
27230    let markdown_language = Arc::new(Language::new(
27231        LanguageConfig {
27232            name: "Markdown".into(),
27233            ..LanguageConfig::default()
27234        },
27235        None,
27236    ));
27237
27238    let mut cx = EditorTestContext::new(cx).await;
27239    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27240    cx.set_state(&format!(
27241        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
27242    ));
27243
27244    cx.update_editor(|editor, window, cx| {
27245        editor.copy(&Copy, window, cx);
27246    });
27247
27248    cx.set_state(&format!(
27249        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
27250    ));
27251
27252    cx.update_editor(|editor, window, cx| {
27253        editor.paste(&Paste, window, cx);
27254    });
27255
27256    cx.assert_editor_state(&format!(
27257        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
27258    ));
27259}
27260
27261#[gpui::test]
27262async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
27263    cx: &mut gpui::TestAppContext,
27264) {
27265    init_test(cx, |_| {});
27266
27267    let url = "https://zed.dev";
27268
27269    let markdown_language = Arc::new(Language::new(
27270        LanguageConfig {
27271            name: "Markdown".into(),
27272            ..LanguageConfig::default()
27273        },
27274        None,
27275    ));
27276
27277    let mut cx = EditorTestContext::new(cx).await;
27278    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27279    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
27280
27281    cx.update_editor(|editor, window, cx| {
27282        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27283        editor.paste(&Paste, window, cx);
27284    });
27285
27286    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
27287}
27288
27289#[gpui::test]
27290async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
27291    cx: &mut gpui::TestAppContext,
27292) {
27293    init_test(cx, |_| {});
27294
27295    let text = "Awesome";
27296
27297    let markdown_language = Arc::new(Language::new(
27298        LanguageConfig {
27299            name: "Markdown".into(),
27300            ..LanguageConfig::default()
27301        },
27302        None,
27303    ));
27304
27305    let mut cx = EditorTestContext::new(cx).await;
27306    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27307    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
27308
27309    cx.update_editor(|editor, window, cx| {
27310        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
27311        editor.paste(&Paste, window, cx);
27312    });
27313
27314    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
27315}
27316
27317#[gpui::test]
27318async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
27319    cx: &mut gpui::TestAppContext,
27320) {
27321    init_test(cx, |_| {});
27322
27323    let url = "https://zed.dev";
27324
27325    let markdown_language = Arc::new(Language::new(
27326        LanguageConfig {
27327            name: "Rust".into(),
27328            ..LanguageConfig::default()
27329        },
27330        None,
27331    ));
27332
27333    let mut cx = EditorTestContext::new(cx).await;
27334    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27335    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
27336
27337    cx.update_editor(|editor, window, cx| {
27338        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27339        editor.paste(&Paste, window, cx);
27340    });
27341
27342    cx.assert_editor_state(&format!(
27343        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
27344    ));
27345}
27346
27347#[gpui::test]
27348async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
27349    cx: &mut TestAppContext,
27350) {
27351    init_test(cx, |_| {});
27352
27353    let url = "https://zed.dev";
27354
27355    let markdown_language = Arc::new(Language::new(
27356        LanguageConfig {
27357            name: "Markdown".into(),
27358            ..LanguageConfig::default()
27359        },
27360        None,
27361    ));
27362
27363    let (editor, cx) = cx.add_window_view(|window, cx| {
27364        let multi_buffer = MultiBuffer::build_multi(
27365            [
27366                ("this will embed -> link", vec![Point::row_range(0..1)]),
27367                ("this will replace -> link", vec![Point::row_range(0..1)]),
27368            ],
27369            cx,
27370        );
27371        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
27372        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27373            s.select_ranges(vec![
27374                Point::new(0, 19)..Point::new(0, 23),
27375                Point::new(1, 21)..Point::new(1, 25),
27376            ])
27377        });
27378        let first_buffer_id = multi_buffer
27379            .read(cx)
27380            .excerpt_buffer_ids()
27381            .into_iter()
27382            .next()
27383            .unwrap();
27384        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
27385        first_buffer.update(cx, |buffer, cx| {
27386            buffer.set_language(Some(markdown_language.clone()), cx);
27387        });
27388
27389        editor
27390    });
27391    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
27392
27393    cx.update_editor(|editor, window, cx| {
27394        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27395        editor.paste(&Paste, window, cx);
27396    });
27397
27398    cx.assert_editor_state(&format!(
27399        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
27400    ));
27401}
27402
27403#[gpui::test]
27404async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
27405    init_test(cx, |_| {});
27406
27407    let fs = FakeFs::new(cx.executor());
27408    fs.insert_tree(
27409        path!("/project"),
27410        json!({
27411            "first.rs": "# First Document\nSome content here.",
27412            "second.rs": "Plain text content for second file.",
27413        }),
27414    )
27415    .await;
27416
27417    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
27418    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27419    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27420
27421    let language = rust_lang();
27422    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27423    language_registry.add(language.clone());
27424    let mut fake_servers = language_registry.register_fake_lsp(
27425        "Rust",
27426        FakeLspAdapter {
27427            ..FakeLspAdapter::default()
27428        },
27429    );
27430
27431    let buffer1 = project
27432        .update(cx, |project, cx| {
27433            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
27434        })
27435        .await
27436        .unwrap();
27437    let buffer2 = project
27438        .update(cx, |project, cx| {
27439            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
27440        })
27441        .await
27442        .unwrap();
27443
27444    let multi_buffer = cx.new(|cx| {
27445        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
27446        multi_buffer.set_excerpts_for_path(
27447            PathKey::for_buffer(&buffer1, cx),
27448            buffer1.clone(),
27449            [Point::zero()..buffer1.read(cx).max_point()],
27450            3,
27451            cx,
27452        );
27453        multi_buffer.set_excerpts_for_path(
27454            PathKey::for_buffer(&buffer2, cx),
27455            buffer2.clone(),
27456            [Point::zero()..buffer1.read(cx).max_point()],
27457            3,
27458            cx,
27459        );
27460        multi_buffer
27461    });
27462
27463    let (editor, cx) = cx.add_window_view(|window, cx| {
27464        Editor::new(
27465            EditorMode::full(),
27466            multi_buffer,
27467            Some(project.clone()),
27468            window,
27469            cx,
27470        )
27471    });
27472
27473    let fake_language_server = fake_servers.next().await.unwrap();
27474
27475    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
27476
27477    let save = editor.update_in(cx, |editor, window, cx| {
27478        assert!(editor.is_dirty(cx));
27479
27480        editor.save(
27481            SaveOptions {
27482                format: true,
27483                autosave: true,
27484            },
27485            project,
27486            window,
27487            cx,
27488        )
27489    });
27490    let (start_edit_tx, start_edit_rx) = oneshot::channel();
27491    let (done_edit_tx, done_edit_rx) = oneshot::channel();
27492    let mut done_edit_rx = Some(done_edit_rx);
27493    let mut start_edit_tx = Some(start_edit_tx);
27494
27495    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
27496        start_edit_tx.take().unwrap().send(()).unwrap();
27497        let done_edit_rx = done_edit_rx.take().unwrap();
27498        async move {
27499            done_edit_rx.await.unwrap();
27500            Ok(None)
27501        }
27502    });
27503
27504    start_edit_rx.await.unwrap();
27505    buffer2
27506        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
27507        .unwrap();
27508
27509    done_edit_tx.send(()).unwrap();
27510
27511    save.await.unwrap();
27512    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
27513}
27514
27515#[track_caller]
27516fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
27517    editor
27518        .all_inlays(cx)
27519        .into_iter()
27520        .filter_map(|inlay| inlay.get_color())
27521        .map(Rgba::from)
27522        .collect()
27523}
27524
27525#[gpui::test]
27526fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
27527    init_test(cx, |_| {});
27528
27529    let editor = cx.add_window(|window, cx| {
27530        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
27531        build_editor(buffer, window, cx)
27532    });
27533
27534    editor
27535        .update(cx, |editor, window, cx| {
27536            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27537                s.select_display_ranges([
27538                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
27539                ])
27540            });
27541
27542            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
27543
27544            assert_eq!(
27545                editor.display_text(cx),
27546                "line1\nline2\nline2",
27547                "Duplicating last line upward should create duplicate above, not on same line"
27548            );
27549
27550            assert_eq!(
27551                editor
27552                    .selections
27553                    .display_ranges(&editor.display_snapshot(cx)),
27554                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
27555                "Selection should move to the duplicated line"
27556            );
27557        })
27558        .unwrap();
27559}
27560
27561#[gpui::test]
27562async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
27563    init_test(cx, |_| {});
27564
27565    let mut cx = EditorTestContext::new(cx).await;
27566
27567    cx.set_state("line1\nline2ˇ");
27568
27569    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27570
27571    let clipboard_text = cx
27572        .read_from_clipboard()
27573        .and_then(|item| item.text().as_deref().map(str::to_string));
27574
27575    assert_eq!(
27576        clipboard_text,
27577        Some("line2\n".to_string()),
27578        "Copying a line without trailing newline should include a newline"
27579    );
27580
27581    cx.set_state("line1\nˇ");
27582
27583    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27584
27585    cx.assert_editor_state("line1\nline2\nˇ");
27586}
27587
27588#[gpui::test]
27589async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27590    init_test(cx, |_| {});
27591
27592    let mut cx = EditorTestContext::new(cx).await;
27593
27594    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27595
27596    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27597
27598    let clipboard_text = cx
27599        .read_from_clipboard()
27600        .and_then(|item| item.text().as_deref().map(str::to_string));
27601
27602    assert_eq!(
27603        clipboard_text,
27604        Some("line1\nline2\nline3\n".to_string()),
27605        "Copying multiple lines should include a single newline between lines"
27606    );
27607
27608    cx.set_state("lineA\nˇ");
27609
27610    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27611
27612    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27613}
27614
27615#[gpui::test]
27616async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27617    init_test(cx, |_| {});
27618
27619    let mut cx = EditorTestContext::new(cx).await;
27620
27621    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27622
27623    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
27624
27625    let clipboard_text = cx
27626        .read_from_clipboard()
27627        .and_then(|item| item.text().as_deref().map(str::to_string));
27628
27629    assert_eq!(
27630        clipboard_text,
27631        Some("line1\nline2\nline3\n".to_string()),
27632        "Copying multiple lines should include a single newline between lines"
27633    );
27634
27635    cx.set_state("lineA\nˇ");
27636
27637    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27638
27639    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27640}
27641
27642#[gpui::test]
27643async fn test_end_of_editor_context(cx: &mut TestAppContext) {
27644    init_test(cx, |_| {});
27645
27646    let mut cx = EditorTestContext::new(cx).await;
27647
27648    cx.set_state("line1\nline2ˇ");
27649    cx.update_editor(|e, window, cx| {
27650        e.set_mode(EditorMode::SingleLine);
27651        assert!(e.key_context(window, cx).contains("end_of_input"));
27652    });
27653    cx.set_state("ˇline1\nline2");
27654    cx.update_editor(|e, window, cx| {
27655        assert!(!e.key_context(window, cx).contains("end_of_input"));
27656    });
27657    cx.set_state("line1ˇ\nline2");
27658    cx.update_editor(|e, window, cx| {
27659        assert!(!e.key_context(window, cx).contains("end_of_input"));
27660    });
27661}
27662
27663#[gpui::test]
27664async fn test_sticky_scroll(cx: &mut TestAppContext) {
27665    init_test(cx, |_| {});
27666    let mut cx = EditorTestContext::new(cx).await;
27667
27668    let buffer = indoc! {"
27669            ˇfn foo() {
27670                let abc = 123;
27671            }
27672            struct Bar;
27673            impl Bar {
27674                fn new() -> Self {
27675                    Self
27676                }
27677            }
27678            fn baz() {
27679            }
27680        "};
27681    cx.set_state(&buffer);
27682
27683    cx.update_editor(|e, _, cx| {
27684        e.buffer()
27685            .read(cx)
27686            .as_singleton()
27687            .unwrap()
27688            .update(cx, |buffer, cx| {
27689                buffer.set_language(Some(rust_lang()), cx);
27690            })
27691    });
27692
27693    let mut sticky_headers = |offset: ScrollOffset| {
27694        cx.update_editor(|e, window, cx| {
27695            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
27696            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), cx)
27697                .into_iter()
27698                .map(
27699                    |StickyHeader {
27700                         start_point,
27701                         offset,
27702                         ..
27703                     }| { (start_point, offset) },
27704                )
27705                .collect::<Vec<_>>()
27706        })
27707    };
27708
27709    let fn_foo = Point { row: 0, column: 0 };
27710    let impl_bar = Point { row: 4, column: 0 };
27711    let fn_new = Point { row: 5, column: 4 };
27712
27713    assert_eq!(sticky_headers(0.0), vec![]);
27714    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
27715    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
27716    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
27717    assert_eq!(sticky_headers(2.0), vec![]);
27718    assert_eq!(sticky_headers(2.5), vec![]);
27719    assert_eq!(sticky_headers(3.0), vec![]);
27720    assert_eq!(sticky_headers(3.5), vec![]);
27721    assert_eq!(sticky_headers(4.0), vec![]);
27722    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27723    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27724    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
27725    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
27726    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
27727    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
27728    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
27729    assert_eq!(sticky_headers(8.0), vec![]);
27730    assert_eq!(sticky_headers(8.5), vec![]);
27731    assert_eq!(sticky_headers(9.0), vec![]);
27732    assert_eq!(sticky_headers(9.5), vec![]);
27733    assert_eq!(sticky_headers(10.0), vec![]);
27734}
27735
27736#[gpui::test]
27737async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
27738    init_test(cx, |_| {});
27739    cx.update(|cx| {
27740        SettingsStore::update_global(cx, |store, cx| {
27741            store.update_user_settings(cx, |settings| {
27742                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
27743                    enabled: Some(true),
27744                })
27745            });
27746        });
27747    });
27748    let mut cx = EditorTestContext::new(cx).await;
27749
27750    let line_height = cx.editor(|editor, window, _cx| {
27751        editor
27752            .style()
27753            .unwrap()
27754            .text
27755            .line_height_in_pixels(window.rem_size())
27756    });
27757
27758    let buffer = indoc! {"
27759            ˇfn foo() {
27760                let abc = 123;
27761            }
27762            struct Bar;
27763            impl Bar {
27764                fn new() -> Self {
27765                    Self
27766                }
27767            }
27768            fn baz() {
27769            }
27770        "};
27771    cx.set_state(&buffer);
27772
27773    cx.update_editor(|e, _, cx| {
27774        e.buffer()
27775            .read(cx)
27776            .as_singleton()
27777            .unwrap()
27778            .update(cx, |buffer, cx| {
27779                buffer.set_language(Some(rust_lang()), cx);
27780            })
27781    });
27782
27783    let fn_foo = || empty_range(0, 0);
27784    let impl_bar = || empty_range(4, 0);
27785    let fn_new = || empty_range(5, 4);
27786
27787    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
27788        cx.update_editor(|e, window, cx| {
27789            e.scroll(
27790                gpui::Point {
27791                    x: 0.,
27792                    y: scroll_offset,
27793                },
27794                None,
27795                window,
27796                cx,
27797            );
27798        });
27799        cx.simulate_click(
27800            gpui::Point {
27801                x: px(0.),
27802                y: click_offset as f32 * line_height,
27803            },
27804            Modifiers::none(),
27805        );
27806        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
27807    };
27808
27809    assert_eq!(
27810        scroll_and_click(
27811            4.5, // impl Bar is halfway off the screen
27812            0.0  // click top of screen
27813        ),
27814        // scrolled to impl Bar
27815        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27816    );
27817
27818    assert_eq!(
27819        scroll_and_click(
27820            4.5,  // impl Bar is halfway off the screen
27821            0.25  // click middle of impl Bar
27822        ),
27823        // scrolled to impl Bar
27824        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27825    );
27826
27827    assert_eq!(
27828        scroll_and_click(
27829            4.5, // impl Bar is halfway off the screen
27830            1.5  // click below impl Bar (e.g. fn new())
27831        ),
27832        // scrolled to fn new() - this is below the impl Bar header which has persisted
27833        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27834    );
27835
27836    assert_eq!(
27837        scroll_and_click(
27838            5.5,  // fn new is halfway underneath impl Bar
27839            0.75  // click on the overlap of impl Bar and fn new()
27840        ),
27841        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27842    );
27843
27844    assert_eq!(
27845        scroll_and_click(
27846            5.5,  // fn new is halfway underneath impl Bar
27847            1.25  // click on the visible part of fn new()
27848        ),
27849        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27850    );
27851
27852    assert_eq!(
27853        scroll_and_click(
27854            1.5, // fn foo is halfway off the screen
27855            0.0  // click top of screen
27856        ),
27857        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
27858    );
27859
27860    assert_eq!(
27861        scroll_and_click(
27862            1.5,  // fn foo is halfway off the screen
27863            0.75  // click visible part of let abc...
27864        )
27865        .0,
27866        // no change in scroll
27867        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
27868        (gpui::Point { x: 0., y: 1.5 })
27869    );
27870}
27871
27872#[gpui::test]
27873async fn test_next_prev_reference(cx: &mut TestAppContext) {
27874    const CYCLE_POSITIONS: &[&'static str] = &[
27875        indoc! {"
27876            fn foo() {
27877                let ˇabc = 123;
27878                let x = abc + 1;
27879                let y = abc + 2;
27880                let z = abc + 2;
27881            }
27882        "},
27883        indoc! {"
27884            fn foo() {
27885                let abc = 123;
27886                let x = ˇabc + 1;
27887                let y = abc + 2;
27888                let z = abc + 2;
27889            }
27890        "},
27891        indoc! {"
27892            fn foo() {
27893                let abc = 123;
27894                let x = abc + 1;
27895                let y = ˇabc + 2;
27896                let z = abc + 2;
27897            }
27898        "},
27899        indoc! {"
27900            fn foo() {
27901                let abc = 123;
27902                let x = abc + 1;
27903                let y = abc + 2;
27904                let z = ˇabc + 2;
27905            }
27906        "},
27907    ];
27908
27909    init_test(cx, |_| {});
27910
27911    let mut cx = EditorLspTestContext::new_rust(
27912        lsp::ServerCapabilities {
27913            references_provider: Some(lsp::OneOf::Left(true)),
27914            ..Default::default()
27915        },
27916        cx,
27917    )
27918    .await;
27919
27920    // importantly, the cursor is in the middle
27921    cx.set_state(indoc! {"
27922        fn foo() {
27923            let aˇbc = 123;
27924            let x = abc + 1;
27925            let y = abc + 2;
27926            let z = abc + 2;
27927        }
27928    "});
27929
27930    let reference_ranges = [
27931        lsp::Position::new(1, 8),
27932        lsp::Position::new(2, 12),
27933        lsp::Position::new(3, 12),
27934        lsp::Position::new(4, 12),
27935    ]
27936    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
27937
27938    cx.lsp
27939        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
27940            Ok(Some(
27941                reference_ranges
27942                    .map(|range| lsp::Location {
27943                        uri: params.text_document_position.text_document.uri.clone(),
27944                        range,
27945                    })
27946                    .to_vec(),
27947            ))
27948        });
27949
27950    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
27951        cx.update_editor(|editor, window, cx| {
27952            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
27953        })
27954        .unwrap()
27955        .await
27956        .unwrap()
27957    };
27958
27959    _move(Direction::Next, 1, &mut cx).await;
27960    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27961
27962    _move(Direction::Next, 1, &mut cx).await;
27963    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27964
27965    _move(Direction::Next, 1, &mut cx).await;
27966    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27967
27968    // loops back to the start
27969    _move(Direction::Next, 1, &mut cx).await;
27970    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27971
27972    // loops back to the end
27973    _move(Direction::Prev, 1, &mut cx).await;
27974    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27975
27976    _move(Direction::Prev, 1, &mut cx).await;
27977    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27978
27979    _move(Direction::Prev, 1, &mut cx).await;
27980    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27981
27982    _move(Direction::Prev, 1, &mut cx).await;
27983    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27984
27985    _move(Direction::Next, 3, &mut cx).await;
27986    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27987
27988    _move(Direction::Prev, 2, &mut cx).await;
27989    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27990}
27991
27992#[gpui::test]
27993async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
27994    init_test(cx, |_| {});
27995
27996    let (editor, cx) = cx.add_window_view(|window, cx| {
27997        let multi_buffer = MultiBuffer::build_multi(
27998            [
27999                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28000                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28001            ],
28002            cx,
28003        );
28004        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28005    });
28006
28007    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28008    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
28009
28010    cx.assert_excerpts_with_selections(indoc! {"
28011        [EXCERPT]
28012        ˇ1
28013        2
28014        3
28015        [EXCERPT]
28016        1
28017        2
28018        3
28019        "});
28020
28021    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
28022    cx.update_editor(|editor, window, cx| {
28023        editor.change_selections(None.into(), window, cx, |s| {
28024            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28025        });
28026    });
28027    cx.assert_excerpts_with_selections(indoc! {"
28028        [EXCERPT]
28029        1
2803028031        3
28032        [EXCERPT]
28033        1
28034        2
28035        3
28036        "});
28037
28038    cx.update_editor(|editor, window, cx| {
28039        editor
28040            .select_all_matches(&SelectAllMatches, window, cx)
28041            .unwrap();
28042    });
28043    cx.assert_excerpts_with_selections(indoc! {"
28044        [EXCERPT]
28045        1
2804628047        3
28048        [EXCERPT]
28049        1
2805028051        3
28052        "});
28053
28054    cx.update_editor(|editor, window, cx| {
28055        editor.handle_input("X", window, cx);
28056    });
28057    cx.assert_excerpts_with_selections(indoc! {"
28058        [EXCERPT]
28059        1
2806028061        3
28062        [EXCERPT]
28063        1
2806428065        3
28066        "});
28067
28068    // Scenario 2: Select "2", then fold second buffer before insertion
28069    cx.update_multibuffer(|mb, cx| {
28070        for buffer_id in buffer_ids.iter() {
28071            let buffer = mb.buffer(*buffer_id).unwrap();
28072            buffer.update(cx, |buffer, cx| {
28073                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28074            });
28075        }
28076    });
28077
28078    // Select "2" and select all matches
28079    cx.update_editor(|editor, window, cx| {
28080        editor.change_selections(None.into(), window, cx, |s| {
28081            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28082        });
28083        editor
28084            .select_all_matches(&SelectAllMatches, window, cx)
28085            .unwrap();
28086    });
28087
28088    // Fold second buffer - should remove selections from folded buffer
28089    cx.update_editor(|editor, _, cx| {
28090        editor.fold_buffer(buffer_ids[1], cx);
28091    });
28092    cx.assert_excerpts_with_selections(indoc! {"
28093        [EXCERPT]
28094        1
2809528096        3
28097        [EXCERPT]
28098        [FOLDED]
28099        "});
28100
28101    // Insert text - should only affect first buffer
28102    cx.update_editor(|editor, window, cx| {
28103        editor.handle_input("Y", window, cx);
28104    });
28105    cx.update_editor(|editor, _, cx| {
28106        editor.unfold_buffer(buffer_ids[1], cx);
28107    });
28108    cx.assert_excerpts_with_selections(indoc! {"
28109        [EXCERPT]
28110        1
2811128112        3
28113        [EXCERPT]
28114        1
28115        2
28116        3
28117        "});
28118
28119    // Scenario 3: Select "2", then fold first buffer before insertion
28120    cx.update_multibuffer(|mb, cx| {
28121        for buffer_id in buffer_ids.iter() {
28122            let buffer = mb.buffer(*buffer_id).unwrap();
28123            buffer.update(cx, |buffer, cx| {
28124                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28125            });
28126        }
28127    });
28128
28129    // Select "2" and select all matches
28130    cx.update_editor(|editor, window, cx| {
28131        editor.change_selections(None.into(), window, cx, |s| {
28132            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28133        });
28134        editor
28135            .select_all_matches(&SelectAllMatches, window, cx)
28136            .unwrap();
28137    });
28138
28139    // Fold first buffer - should remove selections from folded buffer
28140    cx.update_editor(|editor, _, cx| {
28141        editor.fold_buffer(buffer_ids[0], cx);
28142    });
28143    cx.assert_excerpts_with_selections(indoc! {"
28144        [EXCERPT]
28145        [FOLDED]
28146        [EXCERPT]
28147        1
2814828149        3
28150        "});
28151
28152    // Insert text - should only affect second buffer
28153    cx.update_editor(|editor, window, cx| {
28154        editor.handle_input("Z", window, cx);
28155    });
28156    cx.update_editor(|editor, _, cx| {
28157        editor.unfold_buffer(buffer_ids[0], cx);
28158    });
28159    cx.assert_excerpts_with_selections(indoc! {"
28160        [EXCERPT]
28161        1
28162        2
28163        3
28164        [EXCERPT]
28165        1
2816628167        3
28168        "});
28169
28170    // Edge case scenario: fold all buffers, then try to insert
28171    cx.update_editor(|editor, _, cx| {
28172        editor.fold_buffer(buffer_ids[0], cx);
28173        editor.fold_buffer(buffer_ids[1], cx);
28174    });
28175    cx.assert_excerpts_with_selections(indoc! {"
28176        [EXCERPT]
28177        ˇ[FOLDED]
28178        [EXCERPT]
28179        [FOLDED]
28180        "});
28181
28182    // Insert should work via default selection
28183    cx.update_editor(|editor, window, cx| {
28184        editor.handle_input("W", window, cx);
28185    });
28186    cx.update_editor(|editor, _, cx| {
28187        editor.unfold_buffer(buffer_ids[0], cx);
28188        editor.unfold_buffer(buffer_ids[1], cx);
28189    });
28190    cx.assert_excerpts_with_selections(indoc! {"
28191        [EXCERPT]
28192        Wˇ1
28193        2
28194        3
28195        [EXCERPT]
28196        1
28197        Z
28198        3
28199        "});
28200}
28201
28202#[gpui::test]
28203async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
28204    init_test(cx, |_| {});
28205
28206    let (editor, cx) = cx.add_window_view(|window, cx| {
28207        let multi_buffer = MultiBuffer::build_multi(
28208            [
28209                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28210                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
28211            ],
28212            cx,
28213        );
28214        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28215    });
28216
28217    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28218
28219    cx.assert_excerpts_with_selections(indoc! {"
28220        [EXCERPT]
28221        ˇ1
28222        2
28223        3
28224        [EXCERPT]
28225        1
28226        2
28227        3
28228        4
28229        5
28230        6
28231        7
28232        8
28233        9
28234        "});
28235
28236    cx.update_editor(|editor, window, cx| {
28237        editor.change_selections(None.into(), window, cx, |s| {
28238            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
28239        });
28240    });
28241
28242    cx.assert_excerpts_with_selections(indoc! {"
28243        [EXCERPT]
28244        1
28245        2
28246        3
28247        [EXCERPT]
28248        1
28249        2
28250        3
28251        4
28252        5
28253        6
28254        ˇ7
28255        8
28256        9
28257        "});
28258
28259    cx.update_editor(|editor, _window, cx| {
28260        editor.set_vertical_scroll_margin(0, cx);
28261    });
28262
28263    cx.update_editor(|editor, window, cx| {
28264        assert_eq!(editor.vertical_scroll_margin(), 0);
28265        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
28266        assert_eq!(
28267            editor.snapshot(window, cx).scroll_position(),
28268            gpui::Point::new(0., 12.0)
28269        );
28270    });
28271
28272    cx.update_editor(|editor, _window, cx| {
28273        editor.set_vertical_scroll_margin(3, cx);
28274    });
28275
28276    cx.update_editor(|editor, window, cx| {
28277        assert_eq!(editor.vertical_scroll_margin(), 3);
28278        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
28279        assert_eq!(
28280            editor.snapshot(window, cx).scroll_position(),
28281            gpui::Point::new(0., 9.0)
28282        );
28283    });
28284}