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_can_run_commands(cx: &mut TestAppContext) {
14758    init_test(cx, |_| {});
14759
14760    let fs = FakeFs::new(cx.executor());
14761    fs.insert_tree(
14762        path!("/a"),
14763        json!({
14764            "main.rs": "",
14765        }),
14766    )
14767    .await;
14768
14769    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14770    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14771    language_registry.add(rust_lang());
14772    let command_calls = Arc::new(AtomicUsize::new(0));
14773    let registered_command = "_the/command";
14774
14775    let closure_command_calls = command_calls.clone();
14776    let mut fake_servers = language_registry.register_fake_lsp(
14777        "Rust",
14778        FakeLspAdapter {
14779            capabilities: lsp::ServerCapabilities {
14780                completion_provider: Some(lsp::CompletionOptions {
14781                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14782                    ..lsp::CompletionOptions::default()
14783                }),
14784                execute_command_provider: Some(lsp::ExecuteCommandOptions {
14785                    commands: vec![registered_command.to_owned()],
14786                    ..lsp::ExecuteCommandOptions::default()
14787                }),
14788                ..lsp::ServerCapabilities::default()
14789            },
14790            initializer: Some(Box::new(move |fake_server| {
14791                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14792                    move |params, _| async move {
14793                        Ok(Some(lsp::CompletionResponse::Array(vec![
14794                            lsp::CompletionItem {
14795                                label: "registered_command".to_owned(),
14796                                text_edit: gen_text_edit(&params, ""),
14797                                command: Some(lsp::Command {
14798                                    title: registered_command.to_owned(),
14799                                    command: "_the/command".to_owned(),
14800                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
14801                                }),
14802                                ..lsp::CompletionItem::default()
14803                            },
14804                            lsp::CompletionItem {
14805                                label: "unregistered_command".to_owned(),
14806                                text_edit: gen_text_edit(&params, ""),
14807                                command: Some(lsp::Command {
14808                                    title: "????????????".to_owned(),
14809                                    command: "????????????".to_owned(),
14810                                    arguments: Some(vec![serde_json::Value::Null]),
14811                                }),
14812                                ..lsp::CompletionItem::default()
14813                            },
14814                        ])))
14815                    },
14816                );
14817                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
14818                    let command_calls = closure_command_calls.clone();
14819                    move |params, _| {
14820                        assert_eq!(params.command, registered_command);
14821                        let command_calls = command_calls.clone();
14822                        async move {
14823                            command_calls.fetch_add(1, atomic::Ordering::Release);
14824                            Ok(Some(json!(null)))
14825                        }
14826                    }
14827                });
14828            })),
14829            ..FakeLspAdapter::default()
14830        },
14831    );
14832    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14833    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14834    let editor = workspace
14835        .update(cx, |workspace, window, cx| {
14836            workspace.open_abs_path(
14837                PathBuf::from(path!("/a/main.rs")),
14838                OpenOptions::default(),
14839                window,
14840                cx,
14841            )
14842        })
14843        .unwrap()
14844        .await
14845        .unwrap()
14846        .downcast::<Editor>()
14847        .unwrap();
14848    let _fake_server = fake_servers.next().await.unwrap();
14849
14850    editor.update_in(cx, |editor, window, cx| {
14851        cx.focus_self(window);
14852        editor.move_to_end(&MoveToEnd, window, cx);
14853        editor.handle_input(".", window, cx);
14854    });
14855    cx.run_until_parked();
14856    editor.update(cx, |editor, _| {
14857        assert!(editor.context_menu_visible());
14858        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14859        {
14860            let completion_labels = menu
14861                .completions
14862                .borrow()
14863                .iter()
14864                .map(|c| c.label.text.clone())
14865                .collect::<Vec<_>>();
14866            assert_eq!(
14867                completion_labels,
14868                &["registered_command", "unregistered_command",],
14869            );
14870        } else {
14871            panic!("expected completion menu to be open");
14872        }
14873    });
14874
14875    editor
14876        .update_in(cx, |editor, window, cx| {
14877            editor
14878                .confirm_completion(&ConfirmCompletion::default(), window, cx)
14879                .unwrap()
14880        })
14881        .await
14882        .unwrap();
14883    cx.run_until_parked();
14884    assert_eq!(
14885        command_calls.load(atomic::Ordering::Acquire),
14886        1,
14887        "For completion with a registered command, Zed should send a command execution request",
14888    );
14889
14890    editor.update_in(cx, |editor, window, cx| {
14891        cx.focus_self(window);
14892        editor.handle_input(".", window, cx);
14893    });
14894    cx.run_until_parked();
14895    editor.update(cx, |editor, _| {
14896        assert!(editor.context_menu_visible());
14897        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14898        {
14899            let completion_labels = menu
14900                .completions
14901                .borrow()
14902                .iter()
14903                .map(|c| c.label.text.clone())
14904                .collect::<Vec<_>>();
14905            assert_eq!(
14906                completion_labels,
14907                &["registered_command", "unregistered_command",],
14908            );
14909        } else {
14910            panic!("expected completion menu to be open");
14911        }
14912    });
14913    editor
14914        .update_in(cx, |editor, window, cx| {
14915            editor.context_menu_next(&Default::default(), window, cx);
14916            editor
14917                .confirm_completion(&ConfirmCompletion::default(), window, cx)
14918                .unwrap()
14919        })
14920        .await
14921        .unwrap();
14922    cx.run_until_parked();
14923    assert_eq!(
14924        command_calls.load(atomic::Ordering::Acquire),
14925        1,
14926        "For completion with an unregistered command, Zed should not send a command execution request",
14927    );
14928}
14929
14930#[gpui::test]
14931async fn test_completion_reuse(cx: &mut TestAppContext) {
14932    init_test(cx, |_| {});
14933
14934    let mut cx = EditorLspTestContext::new_rust(
14935        lsp::ServerCapabilities {
14936            completion_provider: Some(lsp::CompletionOptions {
14937                trigger_characters: Some(vec![".".to_string()]),
14938                ..Default::default()
14939            }),
14940            ..Default::default()
14941        },
14942        cx,
14943    )
14944    .await;
14945
14946    let counter = Arc::new(AtomicUsize::new(0));
14947    cx.set_state("objˇ");
14948    cx.simulate_keystroke(".");
14949
14950    // Initial completion request returns complete results
14951    let is_incomplete = false;
14952    handle_completion_request(
14953        "obj.|<>",
14954        vec!["a", "ab", "abc"],
14955        is_incomplete,
14956        counter.clone(),
14957        &mut cx,
14958    )
14959    .await;
14960    cx.run_until_parked();
14961    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14962    cx.assert_editor_state("obj.ˇ");
14963    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14964
14965    // Type "a" - filters existing completions
14966    cx.simulate_keystroke("a");
14967    cx.run_until_parked();
14968    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14969    cx.assert_editor_state("obj.aˇ");
14970    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14971
14972    // Type "b" - filters existing completions
14973    cx.simulate_keystroke("b");
14974    cx.run_until_parked();
14975    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14976    cx.assert_editor_state("obj.abˇ");
14977    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14978
14979    // Type "c" - filters existing completions
14980    cx.simulate_keystroke("c");
14981    cx.run_until_parked();
14982    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14983    cx.assert_editor_state("obj.abcˇ");
14984    check_displayed_completions(vec!["abc"], &mut cx);
14985
14986    // Backspace to delete "c" - filters existing completions
14987    cx.update_editor(|editor, window, cx| {
14988        editor.backspace(&Backspace, window, cx);
14989    });
14990    cx.run_until_parked();
14991    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14992    cx.assert_editor_state("obj.abˇ");
14993    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14994
14995    // Moving cursor to the left dismisses menu.
14996    cx.update_editor(|editor, window, cx| {
14997        editor.move_left(&MoveLeft, window, cx);
14998    });
14999    cx.run_until_parked();
15000    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15001    cx.assert_editor_state("obj.aˇb");
15002    cx.update_editor(|editor, _, _| {
15003        assert_eq!(editor.context_menu_visible(), false);
15004    });
15005
15006    // Type "b" - new request
15007    cx.simulate_keystroke("b");
15008    let is_incomplete = false;
15009    handle_completion_request(
15010        "obj.<ab|>a",
15011        vec!["ab", "abc"],
15012        is_incomplete,
15013        counter.clone(),
15014        &mut cx,
15015    )
15016    .await;
15017    cx.run_until_parked();
15018    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15019    cx.assert_editor_state("obj.abˇb");
15020    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15021
15022    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15023    cx.update_editor(|editor, window, cx| {
15024        editor.backspace(&Backspace, window, cx);
15025    });
15026    let is_incomplete = false;
15027    handle_completion_request(
15028        "obj.<a|>b",
15029        vec!["a", "ab", "abc"],
15030        is_incomplete,
15031        counter.clone(),
15032        &mut cx,
15033    )
15034    .await;
15035    cx.run_until_parked();
15036    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15037    cx.assert_editor_state("obj.aˇb");
15038    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15039
15040    // Backspace to delete "a" - dismisses menu.
15041    cx.update_editor(|editor, window, cx| {
15042        editor.backspace(&Backspace, window, cx);
15043    });
15044    cx.run_until_parked();
15045    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15046    cx.assert_editor_state("obj.ˇb");
15047    cx.update_editor(|editor, _, _| {
15048        assert_eq!(editor.context_menu_visible(), false);
15049    });
15050}
15051
15052#[gpui::test]
15053async fn test_word_completion(cx: &mut TestAppContext) {
15054    let lsp_fetch_timeout_ms = 10;
15055    init_test(cx, |language_settings| {
15056        language_settings.defaults.completions = Some(CompletionSettingsContent {
15057            words_min_length: Some(0),
15058            lsp_fetch_timeout_ms: Some(10),
15059            lsp_insert_mode: Some(LspInsertMode::Insert),
15060            ..Default::default()
15061        });
15062    });
15063
15064    let mut cx = EditorLspTestContext::new_rust(
15065        lsp::ServerCapabilities {
15066            completion_provider: Some(lsp::CompletionOptions {
15067                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15068                ..lsp::CompletionOptions::default()
15069            }),
15070            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15071            ..lsp::ServerCapabilities::default()
15072        },
15073        cx,
15074    )
15075    .await;
15076
15077    let throttle_completions = Arc::new(AtomicBool::new(false));
15078
15079    let lsp_throttle_completions = throttle_completions.clone();
15080    let _completion_requests_handler =
15081        cx.lsp
15082            .server
15083            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15084                let lsp_throttle_completions = lsp_throttle_completions.clone();
15085                let cx = cx.clone();
15086                async move {
15087                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15088                        cx.background_executor()
15089                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15090                            .await;
15091                    }
15092                    Ok(Some(lsp::CompletionResponse::Array(vec![
15093                        lsp::CompletionItem {
15094                            label: "first".into(),
15095                            ..lsp::CompletionItem::default()
15096                        },
15097                        lsp::CompletionItem {
15098                            label: "last".into(),
15099                            ..lsp::CompletionItem::default()
15100                        },
15101                    ])))
15102                }
15103            });
15104
15105    cx.set_state(indoc! {"
15106        oneˇ
15107        two
15108        three
15109    "});
15110    cx.simulate_keystroke(".");
15111    cx.executor().run_until_parked();
15112    cx.condition(|editor, _| editor.context_menu_visible())
15113        .await;
15114    cx.update_editor(|editor, window, cx| {
15115        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15116        {
15117            assert_eq!(
15118                completion_menu_entries(menu),
15119                &["first", "last"],
15120                "When LSP server is fast to reply, no fallback word completions are used"
15121            );
15122        } else {
15123            panic!("expected completion menu to be open");
15124        }
15125        editor.cancel(&Cancel, window, cx);
15126    });
15127    cx.executor().run_until_parked();
15128    cx.condition(|editor, _| !editor.context_menu_visible())
15129        .await;
15130
15131    throttle_completions.store(true, atomic::Ordering::Release);
15132    cx.simulate_keystroke(".");
15133    cx.executor()
15134        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15135    cx.executor().run_until_parked();
15136    cx.condition(|editor, _| editor.context_menu_visible())
15137        .await;
15138    cx.update_editor(|editor, _, _| {
15139        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15140        {
15141            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15142                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15143        } else {
15144            panic!("expected completion menu to be open");
15145        }
15146    });
15147}
15148
15149#[gpui::test]
15150async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15151    init_test(cx, |language_settings| {
15152        language_settings.defaults.completions = Some(CompletionSettingsContent {
15153            words: Some(WordsCompletionMode::Enabled),
15154            words_min_length: Some(0),
15155            lsp_insert_mode: Some(LspInsertMode::Insert),
15156            ..Default::default()
15157        });
15158    });
15159
15160    let mut cx = EditorLspTestContext::new_rust(
15161        lsp::ServerCapabilities {
15162            completion_provider: Some(lsp::CompletionOptions {
15163                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15164                ..lsp::CompletionOptions::default()
15165            }),
15166            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15167            ..lsp::ServerCapabilities::default()
15168        },
15169        cx,
15170    )
15171    .await;
15172
15173    let _completion_requests_handler =
15174        cx.lsp
15175            .server
15176            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15177                Ok(Some(lsp::CompletionResponse::Array(vec![
15178                    lsp::CompletionItem {
15179                        label: "first".into(),
15180                        ..lsp::CompletionItem::default()
15181                    },
15182                    lsp::CompletionItem {
15183                        label: "last".into(),
15184                        ..lsp::CompletionItem::default()
15185                    },
15186                ])))
15187            });
15188
15189    cx.set_state(indoc! {"ˇ
15190        first
15191        last
15192        second
15193    "});
15194    cx.simulate_keystroke(".");
15195    cx.executor().run_until_parked();
15196    cx.condition(|editor, _| editor.context_menu_visible())
15197        .await;
15198    cx.update_editor(|editor, _, _| {
15199        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15200        {
15201            assert_eq!(
15202                completion_menu_entries(menu),
15203                &["first", "last", "second"],
15204                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15205            );
15206        } else {
15207            panic!("expected completion menu to be open");
15208        }
15209    });
15210}
15211
15212#[gpui::test]
15213async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15214    init_test(cx, |language_settings| {
15215        language_settings.defaults.completions = Some(CompletionSettingsContent {
15216            words: Some(WordsCompletionMode::Disabled),
15217            words_min_length: Some(0),
15218            lsp_insert_mode: Some(LspInsertMode::Insert),
15219            ..Default::default()
15220        });
15221    });
15222
15223    let mut cx = EditorLspTestContext::new_rust(
15224        lsp::ServerCapabilities {
15225            completion_provider: Some(lsp::CompletionOptions {
15226                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15227                ..lsp::CompletionOptions::default()
15228            }),
15229            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15230            ..lsp::ServerCapabilities::default()
15231        },
15232        cx,
15233    )
15234    .await;
15235
15236    let _completion_requests_handler =
15237        cx.lsp
15238            .server
15239            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15240                panic!("LSP completions should not be queried when dealing with word completions")
15241            });
15242
15243    cx.set_state(indoc! {"ˇ
15244        first
15245        last
15246        second
15247    "});
15248    cx.update_editor(|editor, window, cx| {
15249        editor.show_word_completions(&ShowWordCompletions, window, cx);
15250    });
15251    cx.executor().run_until_parked();
15252    cx.condition(|editor, _| editor.context_menu_visible())
15253        .await;
15254    cx.update_editor(|editor, _, _| {
15255        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15256        {
15257            assert_eq!(
15258                completion_menu_entries(menu),
15259                &["first", "last", "second"],
15260                "`ShowWordCompletions` action should show word completions"
15261            );
15262        } else {
15263            panic!("expected completion menu to be open");
15264        }
15265    });
15266
15267    cx.simulate_keystroke("l");
15268    cx.executor().run_until_parked();
15269    cx.condition(|editor, _| editor.context_menu_visible())
15270        .await;
15271    cx.update_editor(|editor, _, _| {
15272        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15273        {
15274            assert_eq!(
15275                completion_menu_entries(menu),
15276                &["last"],
15277                "After showing word completions, further editing should filter them and not query the LSP"
15278            );
15279        } else {
15280            panic!("expected completion menu to be open");
15281        }
15282    });
15283}
15284
15285#[gpui::test]
15286async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15287    init_test(cx, |language_settings| {
15288        language_settings.defaults.completions = Some(CompletionSettingsContent {
15289            words_min_length: Some(0),
15290            lsp: Some(false),
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
15298    cx.set_state(indoc! {"ˇ
15299        0_usize
15300        let
15301        33
15302        4.5f32
15303    "});
15304    cx.update_editor(|editor, window, cx| {
15305        editor.show_completions(&ShowCompletions, window, cx);
15306    });
15307    cx.executor().run_until_parked();
15308    cx.condition(|editor, _| editor.context_menu_visible())
15309        .await;
15310    cx.update_editor(|editor, window, cx| {
15311        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15312        {
15313            assert_eq!(
15314                completion_menu_entries(menu),
15315                &["let"],
15316                "With no digits in the completion query, no digits should be in the word completions"
15317            );
15318        } else {
15319            panic!("expected completion menu to be open");
15320        }
15321        editor.cancel(&Cancel, window, cx);
15322    });
15323
15324    cx.set_state(indoc! {"15325        0_usize
15326        let
15327        3
15328        33.35f32
15329    "});
15330    cx.update_editor(|editor, window, cx| {
15331        editor.show_completions(&ShowCompletions, window, cx);
15332    });
15333    cx.executor().run_until_parked();
15334    cx.condition(|editor, _| editor.context_menu_visible())
15335        .await;
15336    cx.update_editor(|editor, _, _| {
15337        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15338        {
15339            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15340                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15341        } else {
15342            panic!("expected completion menu to be open");
15343        }
15344    });
15345}
15346
15347#[gpui::test]
15348async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15349    init_test(cx, |language_settings| {
15350        language_settings.defaults.completions = Some(CompletionSettingsContent {
15351            words: Some(WordsCompletionMode::Enabled),
15352            words_min_length: Some(3),
15353            lsp_insert_mode: Some(LspInsertMode::Insert),
15354            ..Default::default()
15355        });
15356    });
15357
15358    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15359    cx.set_state(indoc! {"ˇ
15360        wow
15361        wowen
15362        wowser
15363    "});
15364    cx.simulate_keystroke("w");
15365    cx.executor().run_until_parked();
15366    cx.update_editor(|editor, _, _| {
15367        if editor.context_menu.borrow_mut().is_some() {
15368            panic!(
15369                "expected completion menu to be hidden, as words completion threshold is not met"
15370            );
15371        }
15372    });
15373
15374    cx.update_editor(|editor, window, cx| {
15375        editor.show_word_completions(&ShowWordCompletions, window, cx);
15376    });
15377    cx.executor().run_until_parked();
15378    cx.update_editor(|editor, window, cx| {
15379        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15380        {
15381            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");
15382        } else {
15383            panic!("expected completion menu to be open after the word completions are called with an action");
15384        }
15385
15386        editor.cancel(&Cancel, window, cx);
15387    });
15388    cx.update_editor(|editor, _, _| {
15389        if editor.context_menu.borrow_mut().is_some() {
15390            panic!("expected completion menu to be hidden after canceling");
15391        }
15392    });
15393
15394    cx.simulate_keystroke("o");
15395    cx.executor().run_until_parked();
15396    cx.update_editor(|editor, _, _| {
15397        if editor.context_menu.borrow_mut().is_some() {
15398            panic!(
15399                "expected completion menu to be hidden, as words completion threshold is not met still"
15400            );
15401        }
15402    });
15403
15404    cx.simulate_keystroke("w");
15405    cx.executor().run_until_parked();
15406    cx.update_editor(|editor, _, _| {
15407        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15408        {
15409            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15410        } else {
15411            panic!("expected completion menu to be open after the word completions threshold is met");
15412        }
15413    });
15414}
15415
15416#[gpui::test]
15417async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15418    init_test(cx, |language_settings| {
15419        language_settings.defaults.completions = Some(CompletionSettingsContent {
15420            words: Some(WordsCompletionMode::Enabled),
15421            words_min_length: Some(0),
15422            lsp_insert_mode: Some(LspInsertMode::Insert),
15423            ..Default::default()
15424        });
15425    });
15426
15427    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15428    cx.update_editor(|editor, _, _| {
15429        editor.disable_word_completions();
15430    });
15431    cx.set_state(indoc! {"ˇ
15432        wow
15433        wowen
15434        wowser
15435    "});
15436    cx.simulate_keystroke("w");
15437    cx.executor().run_until_parked();
15438    cx.update_editor(|editor, _, _| {
15439        if editor.context_menu.borrow_mut().is_some() {
15440            panic!(
15441                "expected completion menu to be hidden, as words completion are disabled for this editor"
15442            );
15443        }
15444    });
15445
15446    cx.update_editor(|editor, window, cx| {
15447        editor.show_word_completions(&ShowWordCompletions, window, cx);
15448    });
15449    cx.executor().run_until_parked();
15450    cx.update_editor(|editor, _, _| {
15451        if editor.context_menu.borrow_mut().is_some() {
15452            panic!(
15453                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15454            );
15455        }
15456    });
15457}
15458
15459#[gpui::test]
15460async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15461    init_test(cx, |language_settings| {
15462        language_settings.defaults.completions = Some(CompletionSettingsContent {
15463            words: Some(WordsCompletionMode::Disabled),
15464            words_min_length: Some(0),
15465            lsp_insert_mode: Some(LspInsertMode::Insert),
15466            ..Default::default()
15467        });
15468    });
15469
15470    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15471    cx.update_editor(|editor, _, _| {
15472        editor.set_completion_provider(None);
15473    });
15474    cx.set_state(indoc! {"ˇ
15475        wow
15476        wowen
15477        wowser
15478    "});
15479    cx.simulate_keystroke("w");
15480    cx.executor().run_until_parked();
15481    cx.update_editor(|editor, _, _| {
15482        if editor.context_menu.borrow_mut().is_some() {
15483            panic!("expected completion menu to be hidden, as disabled in settings");
15484        }
15485    });
15486}
15487
15488fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15489    let position = || lsp::Position {
15490        line: params.text_document_position.position.line,
15491        character: params.text_document_position.position.character,
15492    };
15493    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15494        range: lsp::Range {
15495            start: position(),
15496            end: position(),
15497        },
15498        new_text: text.to_string(),
15499    }))
15500}
15501
15502#[gpui::test]
15503async fn test_multiline_completion(cx: &mut TestAppContext) {
15504    init_test(cx, |_| {});
15505
15506    let fs = FakeFs::new(cx.executor());
15507    fs.insert_tree(
15508        path!("/a"),
15509        json!({
15510            "main.ts": "a",
15511        }),
15512    )
15513    .await;
15514
15515    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15516    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15517    let typescript_language = Arc::new(Language::new(
15518        LanguageConfig {
15519            name: "TypeScript".into(),
15520            matcher: LanguageMatcher {
15521                path_suffixes: vec!["ts".to_string()],
15522                ..LanguageMatcher::default()
15523            },
15524            line_comments: vec!["// ".into()],
15525            ..LanguageConfig::default()
15526        },
15527        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15528    ));
15529    language_registry.add(typescript_language.clone());
15530    let mut fake_servers = language_registry.register_fake_lsp(
15531        "TypeScript",
15532        FakeLspAdapter {
15533            capabilities: lsp::ServerCapabilities {
15534                completion_provider: Some(lsp::CompletionOptions {
15535                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15536                    ..lsp::CompletionOptions::default()
15537                }),
15538                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15539                ..lsp::ServerCapabilities::default()
15540            },
15541            // Emulate vtsls label generation
15542            label_for_completion: Some(Box::new(|item, _| {
15543                let text = if let Some(description) = item
15544                    .label_details
15545                    .as_ref()
15546                    .and_then(|label_details| label_details.description.as_ref())
15547                {
15548                    format!("{} {}", item.label, description)
15549                } else if let Some(detail) = &item.detail {
15550                    format!("{} {}", item.label, detail)
15551                } else {
15552                    item.label.clone()
15553                };
15554                Some(language::CodeLabel::plain(text, None))
15555            })),
15556            ..FakeLspAdapter::default()
15557        },
15558    );
15559    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15560    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15561    let worktree_id = workspace
15562        .update(cx, |workspace, _window, cx| {
15563            workspace.project().update(cx, |project, cx| {
15564                project.worktrees(cx).next().unwrap().read(cx).id()
15565            })
15566        })
15567        .unwrap();
15568    let _buffer = project
15569        .update(cx, |project, cx| {
15570            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15571        })
15572        .await
15573        .unwrap();
15574    let editor = workspace
15575        .update(cx, |workspace, window, cx| {
15576            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15577        })
15578        .unwrap()
15579        .await
15580        .unwrap()
15581        .downcast::<Editor>()
15582        .unwrap();
15583    let fake_server = fake_servers.next().await.unwrap();
15584
15585    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15586    let multiline_label_2 = "a\nb\nc\n";
15587    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15588    let multiline_description = "d\ne\nf\n";
15589    let multiline_detail_2 = "g\nh\ni\n";
15590
15591    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15592        move |params, _| async move {
15593            Ok(Some(lsp::CompletionResponse::Array(vec![
15594                lsp::CompletionItem {
15595                    label: multiline_label.to_string(),
15596                    text_edit: gen_text_edit(&params, "new_text_1"),
15597                    ..lsp::CompletionItem::default()
15598                },
15599                lsp::CompletionItem {
15600                    label: "single line label 1".to_string(),
15601                    detail: Some(multiline_detail.to_string()),
15602                    text_edit: gen_text_edit(&params, "new_text_2"),
15603                    ..lsp::CompletionItem::default()
15604                },
15605                lsp::CompletionItem {
15606                    label: "single line label 2".to_string(),
15607                    label_details: Some(lsp::CompletionItemLabelDetails {
15608                        description: Some(multiline_description.to_string()),
15609                        detail: None,
15610                    }),
15611                    text_edit: gen_text_edit(&params, "new_text_2"),
15612                    ..lsp::CompletionItem::default()
15613                },
15614                lsp::CompletionItem {
15615                    label: multiline_label_2.to_string(),
15616                    detail: Some(multiline_detail_2.to_string()),
15617                    text_edit: gen_text_edit(&params, "new_text_3"),
15618                    ..lsp::CompletionItem::default()
15619                },
15620                lsp::CompletionItem {
15621                    label: "Label with many     spaces and \t but without newlines".to_string(),
15622                    detail: Some(
15623                        "Details with many     spaces and \t but without newlines".to_string(),
15624                    ),
15625                    text_edit: gen_text_edit(&params, "new_text_4"),
15626                    ..lsp::CompletionItem::default()
15627                },
15628            ])))
15629        },
15630    );
15631
15632    editor.update_in(cx, |editor, window, cx| {
15633        cx.focus_self(window);
15634        editor.move_to_end(&MoveToEnd, window, cx);
15635        editor.handle_input(".", window, cx);
15636    });
15637    cx.run_until_parked();
15638    completion_handle.next().await.unwrap();
15639
15640    editor.update(cx, |editor, _| {
15641        assert!(editor.context_menu_visible());
15642        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15643        {
15644            let completion_labels = menu
15645                .completions
15646                .borrow()
15647                .iter()
15648                .map(|c| c.label.text.clone())
15649                .collect::<Vec<_>>();
15650            assert_eq!(
15651                completion_labels,
15652                &[
15653                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15654                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15655                    "single line label 2 d e f ",
15656                    "a b c g h i ",
15657                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15658                ],
15659                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15660            );
15661
15662            for completion in menu
15663                .completions
15664                .borrow()
15665                .iter() {
15666                    assert_eq!(
15667                        completion.label.filter_range,
15668                        0..completion.label.text.len(),
15669                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15670                    );
15671                }
15672        } else {
15673            panic!("expected completion menu to be open");
15674        }
15675    });
15676}
15677
15678#[gpui::test]
15679async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15680    init_test(cx, |_| {});
15681    let mut cx = EditorLspTestContext::new_rust(
15682        lsp::ServerCapabilities {
15683            completion_provider: Some(lsp::CompletionOptions {
15684                trigger_characters: Some(vec![".".to_string()]),
15685                ..Default::default()
15686            }),
15687            ..Default::default()
15688        },
15689        cx,
15690    )
15691    .await;
15692    cx.lsp
15693        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15694            Ok(Some(lsp::CompletionResponse::Array(vec![
15695                lsp::CompletionItem {
15696                    label: "first".into(),
15697                    ..Default::default()
15698                },
15699                lsp::CompletionItem {
15700                    label: "last".into(),
15701                    ..Default::default()
15702                },
15703            ])))
15704        });
15705    cx.set_state("variableˇ");
15706    cx.simulate_keystroke(".");
15707    cx.executor().run_until_parked();
15708
15709    cx.update_editor(|editor, _, _| {
15710        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15711        {
15712            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15713        } else {
15714            panic!("expected completion menu to be open");
15715        }
15716    });
15717
15718    cx.update_editor(|editor, window, cx| {
15719        editor.move_page_down(&MovePageDown::default(), window, cx);
15720        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15721        {
15722            assert!(
15723                menu.selected_item == 1,
15724                "expected PageDown to select the last item from the context menu"
15725            );
15726        } else {
15727            panic!("expected completion menu to stay open after PageDown");
15728        }
15729    });
15730
15731    cx.update_editor(|editor, window, cx| {
15732        editor.move_page_up(&MovePageUp::default(), window, cx);
15733        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15734        {
15735            assert!(
15736                menu.selected_item == 0,
15737                "expected PageUp to select the first item from the context menu"
15738            );
15739        } else {
15740            panic!("expected completion menu to stay open after PageUp");
15741        }
15742    });
15743}
15744
15745#[gpui::test]
15746async fn test_as_is_completions(cx: &mut TestAppContext) {
15747    init_test(cx, |_| {});
15748    let mut cx = EditorLspTestContext::new_rust(
15749        lsp::ServerCapabilities {
15750            completion_provider: Some(lsp::CompletionOptions {
15751                ..Default::default()
15752            }),
15753            ..Default::default()
15754        },
15755        cx,
15756    )
15757    .await;
15758    cx.lsp
15759        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15760            Ok(Some(lsp::CompletionResponse::Array(vec![
15761                lsp::CompletionItem {
15762                    label: "unsafe".into(),
15763                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15764                        range: lsp::Range {
15765                            start: lsp::Position {
15766                                line: 1,
15767                                character: 2,
15768                            },
15769                            end: lsp::Position {
15770                                line: 1,
15771                                character: 3,
15772                            },
15773                        },
15774                        new_text: "unsafe".to_string(),
15775                    })),
15776                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15777                    ..Default::default()
15778                },
15779            ])))
15780        });
15781    cx.set_state("fn a() {}\n");
15782    cx.executor().run_until_parked();
15783    cx.update_editor(|editor, window, cx| {
15784        editor.trigger_completion_on_input("n", true, window, cx)
15785    });
15786    cx.executor().run_until_parked();
15787
15788    cx.update_editor(|editor, window, cx| {
15789        editor.confirm_completion(&Default::default(), window, cx)
15790    });
15791    cx.executor().run_until_parked();
15792    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15793}
15794
15795#[gpui::test]
15796async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15797    init_test(cx, |_| {});
15798    let language =
15799        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15800    let mut cx = EditorLspTestContext::new(
15801        language,
15802        lsp::ServerCapabilities {
15803            completion_provider: Some(lsp::CompletionOptions {
15804                ..lsp::CompletionOptions::default()
15805            }),
15806            ..lsp::ServerCapabilities::default()
15807        },
15808        cx,
15809    )
15810    .await;
15811
15812    cx.set_state(
15813        "#ifndef BAR_H
15814#define BAR_H
15815
15816#include <stdbool.h>
15817
15818int fn_branch(bool do_branch1, bool do_branch2);
15819
15820#endif // BAR_H
15821ˇ",
15822    );
15823    cx.executor().run_until_parked();
15824    cx.update_editor(|editor, window, cx| {
15825        editor.handle_input("#", window, cx);
15826    });
15827    cx.executor().run_until_parked();
15828    cx.update_editor(|editor, window, cx| {
15829        editor.handle_input("i", window, cx);
15830    });
15831    cx.executor().run_until_parked();
15832    cx.update_editor(|editor, window, cx| {
15833        editor.handle_input("n", window, cx);
15834    });
15835    cx.executor().run_until_parked();
15836    cx.assert_editor_state(
15837        "#ifndef BAR_H
15838#define BAR_H
15839
15840#include <stdbool.h>
15841
15842int fn_branch(bool do_branch1, bool do_branch2);
15843
15844#endif // BAR_H
15845#inˇ",
15846    );
15847
15848    cx.lsp
15849        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15850            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15851                is_incomplete: false,
15852                item_defaults: None,
15853                items: vec![lsp::CompletionItem {
15854                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15855                    label_details: Some(lsp::CompletionItemLabelDetails {
15856                        detail: Some("header".to_string()),
15857                        description: None,
15858                    }),
15859                    label: " include".to_string(),
15860                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15861                        range: lsp::Range {
15862                            start: lsp::Position {
15863                                line: 8,
15864                                character: 1,
15865                            },
15866                            end: lsp::Position {
15867                                line: 8,
15868                                character: 1,
15869                            },
15870                        },
15871                        new_text: "include \"$0\"".to_string(),
15872                    })),
15873                    sort_text: Some("40b67681include".to_string()),
15874                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15875                    filter_text: Some("include".to_string()),
15876                    insert_text: Some("include \"$0\"".to_string()),
15877                    ..lsp::CompletionItem::default()
15878                }],
15879            })))
15880        });
15881    cx.update_editor(|editor, window, cx| {
15882        editor.show_completions(&ShowCompletions, window, cx);
15883    });
15884    cx.executor().run_until_parked();
15885    cx.update_editor(|editor, window, cx| {
15886        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15887    });
15888    cx.executor().run_until_parked();
15889    cx.assert_editor_state(
15890        "#ifndef BAR_H
15891#define BAR_H
15892
15893#include <stdbool.h>
15894
15895int fn_branch(bool do_branch1, bool do_branch2);
15896
15897#endif // BAR_H
15898#include \"ˇ\"",
15899    );
15900
15901    cx.lsp
15902        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15903            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15904                is_incomplete: true,
15905                item_defaults: None,
15906                items: vec![lsp::CompletionItem {
15907                    kind: Some(lsp::CompletionItemKind::FILE),
15908                    label: "AGL/".to_string(),
15909                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15910                        range: lsp::Range {
15911                            start: lsp::Position {
15912                                line: 8,
15913                                character: 10,
15914                            },
15915                            end: lsp::Position {
15916                                line: 8,
15917                                character: 11,
15918                            },
15919                        },
15920                        new_text: "AGL/".to_string(),
15921                    })),
15922                    sort_text: Some("40b67681AGL/".to_string()),
15923                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15924                    filter_text: Some("AGL/".to_string()),
15925                    insert_text: Some("AGL/".to_string()),
15926                    ..lsp::CompletionItem::default()
15927                }],
15928            })))
15929        });
15930    cx.update_editor(|editor, window, cx| {
15931        editor.show_completions(&ShowCompletions, window, cx);
15932    });
15933    cx.executor().run_until_parked();
15934    cx.update_editor(|editor, window, cx| {
15935        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15936    });
15937    cx.executor().run_until_parked();
15938    cx.assert_editor_state(
15939        r##"#ifndef BAR_H
15940#define BAR_H
15941
15942#include <stdbool.h>
15943
15944int fn_branch(bool do_branch1, bool do_branch2);
15945
15946#endif // BAR_H
15947#include "AGL/ˇ"##,
15948    );
15949
15950    cx.update_editor(|editor, window, cx| {
15951        editor.handle_input("\"", window, cx);
15952    });
15953    cx.executor().run_until_parked();
15954    cx.assert_editor_state(
15955        r##"#ifndef BAR_H
15956#define BAR_H
15957
15958#include <stdbool.h>
15959
15960int fn_branch(bool do_branch1, bool do_branch2);
15961
15962#endif // BAR_H
15963#include "AGL/"ˇ"##,
15964    );
15965}
15966
15967#[gpui::test]
15968async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15969    init_test(cx, |_| {});
15970
15971    let mut cx = EditorLspTestContext::new_rust(
15972        lsp::ServerCapabilities {
15973            completion_provider: Some(lsp::CompletionOptions {
15974                trigger_characters: Some(vec![".".to_string()]),
15975                resolve_provider: Some(true),
15976                ..Default::default()
15977            }),
15978            ..Default::default()
15979        },
15980        cx,
15981    )
15982    .await;
15983
15984    cx.set_state("fn main() { let a = 2ˇ; }");
15985    cx.simulate_keystroke(".");
15986    let completion_item = lsp::CompletionItem {
15987        label: "Some".into(),
15988        kind: Some(lsp::CompletionItemKind::SNIPPET),
15989        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15990        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15991            kind: lsp::MarkupKind::Markdown,
15992            value: "```rust\nSome(2)\n```".to_string(),
15993        })),
15994        deprecated: Some(false),
15995        sort_text: Some("Some".to_string()),
15996        filter_text: Some("Some".to_string()),
15997        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15998        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15999            range: lsp::Range {
16000                start: lsp::Position {
16001                    line: 0,
16002                    character: 22,
16003                },
16004                end: lsp::Position {
16005                    line: 0,
16006                    character: 22,
16007                },
16008            },
16009            new_text: "Some(2)".to_string(),
16010        })),
16011        additional_text_edits: Some(vec![lsp::TextEdit {
16012            range: lsp::Range {
16013                start: lsp::Position {
16014                    line: 0,
16015                    character: 20,
16016                },
16017                end: lsp::Position {
16018                    line: 0,
16019                    character: 22,
16020                },
16021            },
16022            new_text: "".to_string(),
16023        }]),
16024        ..Default::default()
16025    };
16026
16027    let closure_completion_item = completion_item.clone();
16028    let counter = Arc::new(AtomicUsize::new(0));
16029    let counter_clone = counter.clone();
16030    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16031        let task_completion_item = closure_completion_item.clone();
16032        counter_clone.fetch_add(1, atomic::Ordering::Release);
16033        async move {
16034            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16035                is_incomplete: true,
16036                item_defaults: None,
16037                items: vec![task_completion_item],
16038            })))
16039        }
16040    });
16041
16042    cx.condition(|editor, _| editor.context_menu_visible())
16043        .await;
16044    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16045    assert!(request.next().await.is_some());
16046    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16047
16048    cx.simulate_keystrokes("S o m");
16049    cx.condition(|editor, _| editor.context_menu_visible())
16050        .await;
16051    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16052    assert!(request.next().await.is_some());
16053    assert!(request.next().await.is_some());
16054    assert!(request.next().await.is_some());
16055    request.close();
16056    assert!(request.next().await.is_none());
16057    assert_eq!(
16058        counter.load(atomic::Ordering::Acquire),
16059        4,
16060        "With the completions menu open, only one LSP request should happen per input"
16061    );
16062}
16063
16064#[gpui::test]
16065async fn test_toggle_comment(cx: &mut TestAppContext) {
16066    init_test(cx, |_| {});
16067    let mut cx = EditorTestContext::new(cx).await;
16068    let language = Arc::new(Language::new(
16069        LanguageConfig {
16070            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16071            ..Default::default()
16072        },
16073        Some(tree_sitter_rust::LANGUAGE.into()),
16074    ));
16075    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16076
16077    // If multiple selections intersect a line, the line is only toggled once.
16078    cx.set_state(indoc! {"
16079        fn a() {
16080            «//b();
16081            ˇ»// «c();
16082            //ˇ»  d();
16083        }
16084    "});
16085
16086    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16087
16088    cx.assert_editor_state(indoc! {"
16089        fn a() {
16090            «b();
16091            c();
16092            ˇ» d();
16093        }
16094    "});
16095
16096    // The comment prefix is inserted at the same column for every line in a
16097    // selection.
16098    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16099
16100    cx.assert_editor_state(indoc! {"
16101        fn a() {
16102            // «b();
16103            // c();
16104            ˇ»//  d();
16105        }
16106    "});
16107
16108    // If a selection ends at the beginning of a line, that line is not toggled.
16109    cx.set_selections_state(indoc! {"
16110        fn a() {
16111            // b();
16112            «// c();
16113        ˇ»    //  d();
16114        }
16115    "});
16116
16117    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16118
16119    cx.assert_editor_state(indoc! {"
16120        fn a() {
16121            // b();
16122            «c();
16123        ˇ»    //  d();
16124        }
16125    "});
16126
16127    // If a selection span a single line and is empty, the line is toggled.
16128    cx.set_state(indoc! {"
16129        fn a() {
16130            a();
16131            b();
16132        ˇ
16133        }
16134    "});
16135
16136    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16137
16138    cx.assert_editor_state(indoc! {"
16139        fn a() {
16140            a();
16141            b();
16142        //•ˇ
16143        }
16144    "});
16145
16146    // If a selection span multiple lines, empty lines are not toggled.
16147    cx.set_state(indoc! {"
16148        fn a() {
16149            «a();
16150
16151            c();ˇ»
16152        }
16153    "});
16154
16155    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16156
16157    cx.assert_editor_state(indoc! {"
16158        fn a() {
16159            // «a();
16160
16161            // c();ˇ»
16162        }
16163    "});
16164
16165    // If a selection includes multiple comment prefixes, all lines are uncommented.
16166    cx.set_state(indoc! {"
16167        fn a() {
16168            «// a();
16169            /// b();
16170            //! c();ˇ»
16171        }
16172    "});
16173
16174    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16175
16176    cx.assert_editor_state(indoc! {"
16177        fn a() {
16178            «a();
16179            b();
16180            c();ˇ»
16181        }
16182    "});
16183}
16184
16185#[gpui::test]
16186async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16187    init_test(cx, |_| {});
16188    let mut cx = EditorTestContext::new(cx).await;
16189    let language = Arc::new(Language::new(
16190        LanguageConfig {
16191            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16192            ..Default::default()
16193        },
16194        Some(tree_sitter_rust::LANGUAGE.into()),
16195    ));
16196    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16197
16198    let toggle_comments = &ToggleComments {
16199        advance_downwards: false,
16200        ignore_indent: true,
16201    };
16202
16203    // If multiple selections intersect a line, the line is only toggled once.
16204    cx.set_state(indoc! {"
16205        fn a() {
16206        //    «b();
16207        //    c();
16208        //    ˇ» d();
16209        }
16210    "});
16211
16212    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16213
16214    cx.assert_editor_state(indoc! {"
16215        fn a() {
16216            «b();
16217            c();
16218            ˇ» d();
16219        }
16220    "});
16221
16222    // The comment prefix is inserted at the beginning of each line
16223    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16224
16225    cx.assert_editor_state(indoc! {"
16226        fn a() {
16227        //    «b();
16228        //    c();
16229        //    ˇ» d();
16230        }
16231    "});
16232
16233    // If a selection ends at the beginning of a line, that line is not toggled.
16234    cx.set_selections_state(indoc! {"
16235        fn a() {
16236        //    b();
16237        //    «c();
16238        ˇ»//     d();
16239        }
16240    "});
16241
16242    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16243
16244    cx.assert_editor_state(indoc! {"
16245        fn a() {
16246        //    b();
16247            «c();
16248        ˇ»//     d();
16249        }
16250    "});
16251
16252    // If a selection span a single line and is empty, the line is toggled.
16253    cx.set_state(indoc! {"
16254        fn a() {
16255            a();
16256            b();
16257        ˇ
16258        }
16259    "});
16260
16261    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16262
16263    cx.assert_editor_state(indoc! {"
16264        fn a() {
16265            a();
16266            b();
16267        //ˇ
16268        }
16269    "});
16270
16271    // If a selection span multiple lines, empty lines are not toggled.
16272    cx.set_state(indoc! {"
16273        fn a() {
16274            «a();
16275
16276            c();ˇ»
16277        }
16278    "});
16279
16280    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16281
16282    cx.assert_editor_state(indoc! {"
16283        fn a() {
16284        //    «a();
16285
16286        //    c();ˇ»
16287        }
16288    "});
16289
16290    // If a selection includes multiple comment prefixes, all lines are uncommented.
16291    cx.set_state(indoc! {"
16292        fn a() {
16293        //    «a();
16294        ///    b();
16295        //!    c();ˇ»
16296        }
16297    "});
16298
16299    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16300
16301    cx.assert_editor_state(indoc! {"
16302        fn a() {
16303            «a();
16304            b();
16305            c();ˇ»
16306        }
16307    "});
16308}
16309
16310#[gpui::test]
16311async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16312    init_test(cx, |_| {});
16313
16314    let language = Arc::new(Language::new(
16315        LanguageConfig {
16316            line_comments: vec!["// ".into()],
16317            ..Default::default()
16318        },
16319        Some(tree_sitter_rust::LANGUAGE.into()),
16320    ));
16321
16322    let mut cx = EditorTestContext::new(cx).await;
16323
16324    cx.language_registry().add(language.clone());
16325    cx.update_buffer(|buffer, cx| {
16326        buffer.set_language(Some(language), cx);
16327    });
16328
16329    let toggle_comments = &ToggleComments {
16330        advance_downwards: true,
16331        ignore_indent: false,
16332    };
16333
16334    // Single cursor on one line -> advance
16335    // Cursor moves horizontally 3 characters as well on non-blank line
16336    cx.set_state(indoc!(
16337        "fn a() {
16338             ˇdog();
16339             cat();
16340        }"
16341    ));
16342    cx.update_editor(|editor, window, cx| {
16343        editor.toggle_comments(toggle_comments, window, cx);
16344    });
16345    cx.assert_editor_state(indoc!(
16346        "fn a() {
16347             // dog();
16348             catˇ();
16349        }"
16350    ));
16351
16352    // Single selection on one line -> don't advance
16353    cx.set_state(indoc!(
16354        "fn a() {
16355             «dog()ˇ»;
16356             cat();
16357        }"
16358    ));
16359    cx.update_editor(|editor, window, cx| {
16360        editor.toggle_comments(toggle_comments, window, cx);
16361    });
16362    cx.assert_editor_state(indoc!(
16363        "fn a() {
16364             // «dog()ˇ»;
16365             cat();
16366        }"
16367    ));
16368
16369    // Multiple cursors on one line -> advance
16370    cx.set_state(indoc!(
16371        "fn a() {
16372             ˇdˇog();
16373             cat();
16374        }"
16375    ));
16376    cx.update_editor(|editor, window, cx| {
16377        editor.toggle_comments(toggle_comments, window, cx);
16378    });
16379    cx.assert_editor_state(indoc!(
16380        "fn a() {
16381             // dog();
16382             catˇ(ˇ);
16383        }"
16384    ));
16385
16386    // Multiple cursors on one line, with selection -> don't advance
16387    cx.set_state(indoc!(
16388        "fn a() {
16389             ˇdˇog«()ˇ»;
16390             cat();
16391        }"
16392    ));
16393    cx.update_editor(|editor, window, cx| {
16394        editor.toggle_comments(toggle_comments, window, cx);
16395    });
16396    cx.assert_editor_state(indoc!(
16397        "fn a() {
16398             // ˇdˇog«()ˇ»;
16399             cat();
16400        }"
16401    ));
16402
16403    // Single cursor on one line -> advance
16404    // Cursor moves to column 0 on blank line
16405    cx.set_state(indoc!(
16406        "fn a() {
16407             ˇdog();
16408
16409             cat();
16410        }"
16411    ));
16412    cx.update_editor(|editor, window, cx| {
16413        editor.toggle_comments(toggle_comments, window, cx);
16414    });
16415    cx.assert_editor_state(indoc!(
16416        "fn a() {
16417             // dog();
16418        ˇ
16419             cat();
16420        }"
16421    ));
16422
16423    // Single cursor on one line -> advance
16424    // Cursor starts and ends at column 0
16425    cx.set_state(indoc!(
16426        "fn a() {
16427         ˇ    dog();
16428             cat();
16429        }"
16430    ));
16431    cx.update_editor(|editor, window, cx| {
16432        editor.toggle_comments(toggle_comments, window, cx);
16433    });
16434    cx.assert_editor_state(indoc!(
16435        "fn a() {
16436             // dog();
16437         ˇ    cat();
16438        }"
16439    ));
16440}
16441
16442#[gpui::test]
16443async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16444    init_test(cx, |_| {});
16445
16446    let mut cx = EditorTestContext::new(cx).await;
16447
16448    let html_language = Arc::new(
16449        Language::new(
16450            LanguageConfig {
16451                name: "HTML".into(),
16452                block_comment: Some(BlockCommentConfig {
16453                    start: "<!-- ".into(),
16454                    prefix: "".into(),
16455                    end: " -->".into(),
16456                    tab_size: 0,
16457                }),
16458                ..Default::default()
16459            },
16460            Some(tree_sitter_html::LANGUAGE.into()),
16461        )
16462        .with_injection_query(
16463            r#"
16464            (script_element
16465                (raw_text) @injection.content
16466                (#set! injection.language "javascript"))
16467            "#,
16468        )
16469        .unwrap(),
16470    );
16471
16472    let javascript_language = Arc::new(Language::new(
16473        LanguageConfig {
16474            name: "JavaScript".into(),
16475            line_comments: vec!["// ".into()],
16476            ..Default::default()
16477        },
16478        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16479    ));
16480
16481    cx.language_registry().add(html_language.clone());
16482    cx.language_registry().add(javascript_language);
16483    cx.update_buffer(|buffer, cx| {
16484        buffer.set_language(Some(html_language), cx);
16485    });
16486
16487    // Toggle comments for empty selections
16488    cx.set_state(
16489        &r#"
16490            <p>A</p>ˇ
16491            <p>B</p>ˇ
16492            <p>C</p>ˇ
16493        "#
16494        .unindent(),
16495    );
16496    cx.update_editor(|editor, window, cx| {
16497        editor.toggle_comments(&ToggleComments::default(), window, cx)
16498    });
16499    cx.assert_editor_state(
16500        &r#"
16501            <!-- <p>A</p>ˇ -->
16502            <!-- <p>B</p>ˇ -->
16503            <!-- <p>C</p>ˇ -->
16504        "#
16505        .unindent(),
16506    );
16507    cx.update_editor(|editor, window, cx| {
16508        editor.toggle_comments(&ToggleComments::default(), window, cx)
16509    });
16510    cx.assert_editor_state(
16511        &r#"
16512            <p>A</p>ˇ
16513            <p>B</p>ˇ
16514            <p>C</p>ˇ
16515        "#
16516        .unindent(),
16517    );
16518
16519    // Toggle comments for mixture of empty and non-empty selections, where
16520    // multiple selections occupy a given line.
16521    cx.set_state(
16522        &r#"
16523            <p>A«</p>
16524            <p>ˇ»B</p>ˇ
16525            <p>C«</p>
16526            <p>ˇ»D</p>ˇ
16527        "#
16528        .unindent(),
16529    );
16530
16531    cx.update_editor(|editor, window, cx| {
16532        editor.toggle_comments(&ToggleComments::default(), window, cx)
16533    });
16534    cx.assert_editor_state(
16535        &r#"
16536            <!-- <p>A«</p>
16537            <p>ˇ»B</p>ˇ -->
16538            <!-- <p>C«</p>
16539            <p>ˇ»D</p>ˇ -->
16540        "#
16541        .unindent(),
16542    );
16543    cx.update_editor(|editor, window, cx| {
16544        editor.toggle_comments(&ToggleComments::default(), window, cx)
16545    });
16546    cx.assert_editor_state(
16547        &r#"
16548            <p>A«</p>
16549            <p>ˇ»B</p>ˇ
16550            <p>C«</p>
16551            <p>ˇ»D</p>ˇ
16552        "#
16553        .unindent(),
16554    );
16555
16556    // Toggle comments when different languages are active for different
16557    // selections.
16558    cx.set_state(
16559        &r#"
16560            ˇ<script>
16561                ˇvar x = new Y();
16562            ˇ</script>
16563        "#
16564        .unindent(),
16565    );
16566    cx.executor().run_until_parked();
16567    cx.update_editor(|editor, window, cx| {
16568        editor.toggle_comments(&ToggleComments::default(), window, cx)
16569    });
16570    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16571    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16572    cx.assert_editor_state(
16573        &r#"
16574            <!-- ˇ<script> -->
16575                // ˇvar x = new Y();
16576            <!-- ˇ</script> -->
16577        "#
16578        .unindent(),
16579    );
16580}
16581
16582#[gpui::test]
16583fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16584    init_test(cx, |_| {});
16585
16586    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16587    let multibuffer = cx.new(|cx| {
16588        let mut multibuffer = MultiBuffer::new(ReadWrite);
16589        multibuffer.push_excerpts(
16590            buffer.clone(),
16591            [
16592                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16593                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16594            ],
16595            cx,
16596        );
16597        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16598        multibuffer
16599    });
16600
16601    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16602    editor.update_in(cx, |editor, window, cx| {
16603        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16604        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16605            s.select_ranges([
16606                Point::new(0, 0)..Point::new(0, 0),
16607                Point::new(1, 0)..Point::new(1, 0),
16608            ])
16609        });
16610
16611        editor.handle_input("X", window, cx);
16612        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16613        assert_eq!(
16614            editor.selections.ranges(&editor.display_snapshot(cx)),
16615            [
16616                Point::new(0, 1)..Point::new(0, 1),
16617                Point::new(1, 1)..Point::new(1, 1),
16618            ]
16619        );
16620
16621        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16622        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16623            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16624        });
16625        editor.backspace(&Default::default(), window, cx);
16626        assert_eq!(editor.text(cx), "Xa\nbbb");
16627        assert_eq!(
16628            editor.selections.ranges(&editor.display_snapshot(cx)),
16629            [Point::new(1, 0)..Point::new(1, 0)]
16630        );
16631
16632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16633            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16634        });
16635        editor.backspace(&Default::default(), window, cx);
16636        assert_eq!(editor.text(cx), "X\nbb");
16637        assert_eq!(
16638            editor.selections.ranges(&editor.display_snapshot(cx)),
16639            [Point::new(0, 1)..Point::new(0, 1)]
16640        );
16641    });
16642}
16643
16644#[gpui::test]
16645fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16646    init_test(cx, |_| {});
16647
16648    let markers = vec![('[', ']').into(), ('(', ')').into()];
16649    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16650        indoc! {"
16651            [aaaa
16652            (bbbb]
16653            cccc)",
16654        },
16655        markers.clone(),
16656    );
16657    let excerpt_ranges = markers.into_iter().map(|marker| {
16658        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16659        ExcerptRange::new(context)
16660    });
16661    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16662    let multibuffer = cx.new(|cx| {
16663        let mut multibuffer = MultiBuffer::new(ReadWrite);
16664        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16665        multibuffer
16666    });
16667
16668    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16669    editor.update_in(cx, |editor, window, cx| {
16670        let (expected_text, selection_ranges) = marked_text_ranges(
16671            indoc! {"
16672                aaaa
16673                bˇbbb
16674                bˇbbˇb
16675                cccc"
16676            },
16677            true,
16678        );
16679        assert_eq!(editor.text(cx), expected_text);
16680        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16681            s.select_ranges(
16682                selection_ranges
16683                    .iter()
16684                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
16685            )
16686        });
16687
16688        editor.handle_input("X", window, cx);
16689
16690        let (expected_text, expected_selections) = marked_text_ranges(
16691            indoc! {"
16692                aaaa
16693                bXˇbbXb
16694                bXˇbbXˇb
16695                cccc"
16696            },
16697            false,
16698        );
16699        assert_eq!(editor.text(cx), expected_text);
16700        assert_eq!(
16701            editor.selections.ranges(&editor.display_snapshot(cx)),
16702            expected_selections
16703                .iter()
16704                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16705                .collect::<Vec<_>>()
16706        );
16707
16708        editor.newline(&Newline, window, cx);
16709        let (expected_text, expected_selections) = marked_text_ranges(
16710            indoc! {"
16711                aaaa
16712                bX
16713                ˇbbX
16714                b
16715                bX
16716                ˇbbX
16717                ˇb
16718                cccc"
16719            },
16720            false,
16721        );
16722        assert_eq!(editor.text(cx), expected_text);
16723        assert_eq!(
16724            editor.selections.ranges(&editor.display_snapshot(cx)),
16725            expected_selections
16726                .iter()
16727                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16728                .collect::<Vec<_>>()
16729        );
16730    });
16731}
16732
16733#[gpui::test]
16734fn test_refresh_selections(cx: &mut TestAppContext) {
16735    init_test(cx, |_| {});
16736
16737    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16738    let mut excerpt1_id = None;
16739    let multibuffer = cx.new(|cx| {
16740        let mut multibuffer = MultiBuffer::new(ReadWrite);
16741        excerpt1_id = multibuffer
16742            .push_excerpts(
16743                buffer.clone(),
16744                [
16745                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16746                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16747                ],
16748                cx,
16749            )
16750            .into_iter()
16751            .next();
16752        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16753        multibuffer
16754    });
16755
16756    let editor = cx.add_window(|window, cx| {
16757        let mut editor = build_editor(multibuffer.clone(), window, cx);
16758        let snapshot = editor.snapshot(window, cx);
16759        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16760            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16761        });
16762        editor.begin_selection(
16763            Point::new(2, 1).to_display_point(&snapshot),
16764            true,
16765            1,
16766            window,
16767            cx,
16768        );
16769        assert_eq!(
16770            editor.selections.ranges(&editor.display_snapshot(cx)),
16771            [
16772                Point::new(1, 3)..Point::new(1, 3),
16773                Point::new(2, 1)..Point::new(2, 1),
16774            ]
16775        );
16776        editor
16777    });
16778
16779    // Refreshing selections is a no-op when excerpts haven't changed.
16780    _ = editor.update(cx, |editor, window, cx| {
16781        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16782        assert_eq!(
16783            editor.selections.ranges(&editor.display_snapshot(cx)),
16784            [
16785                Point::new(1, 3)..Point::new(1, 3),
16786                Point::new(2, 1)..Point::new(2, 1),
16787            ]
16788        );
16789    });
16790
16791    multibuffer.update(cx, |multibuffer, cx| {
16792        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16793    });
16794    _ = editor.update(cx, |editor, window, cx| {
16795        // Removing an excerpt causes the first selection to become degenerate.
16796        assert_eq!(
16797            editor.selections.ranges(&editor.display_snapshot(cx)),
16798            [
16799                Point::new(0, 0)..Point::new(0, 0),
16800                Point::new(0, 1)..Point::new(0, 1)
16801            ]
16802        );
16803
16804        // Refreshing selections will relocate the first selection to the original buffer
16805        // location.
16806        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16807        assert_eq!(
16808            editor.selections.ranges(&editor.display_snapshot(cx)),
16809            [
16810                Point::new(0, 1)..Point::new(0, 1),
16811                Point::new(0, 3)..Point::new(0, 3)
16812            ]
16813        );
16814        assert!(editor.selections.pending_anchor().is_some());
16815    });
16816}
16817
16818#[gpui::test]
16819fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16820    init_test(cx, |_| {});
16821
16822    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16823    let mut excerpt1_id = None;
16824    let multibuffer = cx.new(|cx| {
16825        let mut multibuffer = MultiBuffer::new(ReadWrite);
16826        excerpt1_id = multibuffer
16827            .push_excerpts(
16828                buffer.clone(),
16829                [
16830                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16831                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16832                ],
16833                cx,
16834            )
16835            .into_iter()
16836            .next();
16837        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16838        multibuffer
16839    });
16840
16841    let editor = cx.add_window(|window, cx| {
16842        let mut editor = build_editor(multibuffer.clone(), window, cx);
16843        let snapshot = editor.snapshot(window, cx);
16844        editor.begin_selection(
16845            Point::new(1, 3).to_display_point(&snapshot),
16846            false,
16847            1,
16848            window,
16849            cx,
16850        );
16851        assert_eq!(
16852            editor.selections.ranges(&editor.display_snapshot(cx)),
16853            [Point::new(1, 3)..Point::new(1, 3)]
16854        );
16855        editor
16856    });
16857
16858    multibuffer.update(cx, |multibuffer, cx| {
16859        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16860    });
16861    _ = editor.update(cx, |editor, window, cx| {
16862        assert_eq!(
16863            editor.selections.ranges(&editor.display_snapshot(cx)),
16864            [Point::new(0, 0)..Point::new(0, 0)]
16865        );
16866
16867        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16868        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16869        assert_eq!(
16870            editor.selections.ranges(&editor.display_snapshot(cx)),
16871            [Point::new(0, 3)..Point::new(0, 3)]
16872        );
16873        assert!(editor.selections.pending_anchor().is_some());
16874    });
16875}
16876
16877#[gpui::test]
16878async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16879    init_test(cx, |_| {});
16880
16881    let language = Arc::new(
16882        Language::new(
16883            LanguageConfig {
16884                brackets: BracketPairConfig {
16885                    pairs: vec![
16886                        BracketPair {
16887                            start: "{".to_string(),
16888                            end: "}".to_string(),
16889                            close: true,
16890                            surround: true,
16891                            newline: true,
16892                        },
16893                        BracketPair {
16894                            start: "/* ".to_string(),
16895                            end: " */".to_string(),
16896                            close: true,
16897                            surround: true,
16898                            newline: true,
16899                        },
16900                    ],
16901                    ..Default::default()
16902                },
16903                ..Default::default()
16904            },
16905            Some(tree_sitter_rust::LANGUAGE.into()),
16906        )
16907        .with_indents_query("")
16908        .unwrap(),
16909    );
16910
16911    let text = concat!(
16912        "{   }\n",     //
16913        "  x\n",       //
16914        "  /*   */\n", //
16915        "x\n",         //
16916        "{{} }\n",     //
16917    );
16918
16919    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16920    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16921    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16922    editor
16923        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16924        .await;
16925
16926    editor.update_in(cx, |editor, window, cx| {
16927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16928            s.select_display_ranges([
16929                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16930                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16931                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16932            ])
16933        });
16934        editor.newline(&Newline, window, cx);
16935
16936        assert_eq!(
16937            editor.buffer().read(cx).read(cx).text(),
16938            concat!(
16939                "{ \n",    // Suppress rustfmt
16940                "\n",      //
16941                "}\n",     //
16942                "  x\n",   //
16943                "  /* \n", //
16944                "  \n",    //
16945                "  */\n",  //
16946                "x\n",     //
16947                "{{} \n",  //
16948                "}\n",     //
16949            )
16950        );
16951    });
16952}
16953
16954#[gpui::test]
16955fn test_highlighted_ranges(cx: &mut TestAppContext) {
16956    init_test(cx, |_| {});
16957
16958    let editor = cx.add_window(|window, cx| {
16959        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16960        build_editor(buffer, window, cx)
16961    });
16962
16963    _ = editor.update(cx, |editor, window, cx| {
16964        struct Type1;
16965        struct Type2;
16966
16967        let buffer = editor.buffer.read(cx).snapshot(cx);
16968
16969        let anchor_range =
16970            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16971
16972        editor.highlight_background::<Type1>(
16973            &[
16974                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16975                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16976                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16977                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16978            ],
16979            |_| Hsla::red(),
16980            cx,
16981        );
16982        editor.highlight_background::<Type2>(
16983            &[
16984                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16985                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16986                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16987                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16988            ],
16989            |_| Hsla::green(),
16990            cx,
16991        );
16992
16993        let snapshot = editor.snapshot(window, cx);
16994        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16995            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16996            &snapshot,
16997            cx.theme(),
16998        );
16999        assert_eq!(
17000            highlighted_ranges,
17001            &[
17002                (
17003                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17004                    Hsla::green(),
17005                ),
17006                (
17007                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17008                    Hsla::red(),
17009                ),
17010                (
17011                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17012                    Hsla::green(),
17013                ),
17014                (
17015                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17016                    Hsla::red(),
17017                ),
17018            ]
17019        );
17020        assert_eq!(
17021            editor.sorted_background_highlights_in_range(
17022                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17023                &snapshot,
17024                cx.theme(),
17025            ),
17026            &[(
17027                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17028                Hsla::red(),
17029            )]
17030        );
17031    });
17032}
17033
17034#[gpui::test]
17035async fn test_following(cx: &mut TestAppContext) {
17036    init_test(cx, |_| {});
17037
17038    let fs = FakeFs::new(cx.executor());
17039    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17040
17041    let buffer = project.update(cx, |project, cx| {
17042        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17043        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17044    });
17045    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17046    let follower = cx.update(|cx| {
17047        cx.open_window(
17048            WindowOptions {
17049                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17050                    gpui::Point::new(px(0.), px(0.)),
17051                    gpui::Point::new(px(10.), px(80.)),
17052                ))),
17053                ..Default::default()
17054            },
17055            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17056        )
17057        .unwrap()
17058    });
17059
17060    let is_still_following = Rc::new(RefCell::new(true));
17061    let follower_edit_event_count = Rc::new(RefCell::new(0));
17062    let pending_update = Rc::new(RefCell::new(None));
17063    let leader_entity = leader.root(cx).unwrap();
17064    let follower_entity = follower.root(cx).unwrap();
17065    _ = follower.update(cx, {
17066        let update = pending_update.clone();
17067        let is_still_following = is_still_following.clone();
17068        let follower_edit_event_count = follower_edit_event_count.clone();
17069        |_, window, cx| {
17070            cx.subscribe_in(
17071                &leader_entity,
17072                window,
17073                move |_, leader, event, window, cx| {
17074                    leader.read(cx).add_event_to_update_proto(
17075                        event,
17076                        &mut update.borrow_mut(),
17077                        window,
17078                        cx,
17079                    );
17080                },
17081            )
17082            .detach();
17083
17084            cx.subscribe_in(
17085                &follower_entity,
17086                window,
17087                move |_, _, event: &EditorEvent, _window, _cx| {
17088                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17089                        *is_still_following.borrow_mut() = false;
17090                    }
17091
17092                    if let EditorEvent::BufferEdited = event {
17093                        *follower_edit_event_count.borrow_mut() += 1;
17094                    }
17095                },
17096            )
17097            .detach();
17098        }
17099    });
17100
17101    // Update the selections only
17102    _ = leader.update(cx, |leader, window, cx| {
17103        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17104            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17105        });
17106    });
17107    follower
17108        .update(cx, |follower, window, cx| {
17109            follower.apply_update_proto(
17110                &project,
17111                pending_update.borrow_mut().take().unwrap(),
17112                window,
17113                cx,
17114            )
17115        })
17116        .unwrap()
17117        .await
17118        .unwrap();
17119    _ = follower.update(cx, |follower, _, cx| {
17120        assert_eq!(
17121            follower.selections.ranges(&follower.display_snapshot(cx)),
17122            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17123        );
17124    });
17125    assert!(*is_still_following.borrow());
17126    assert_eq!(*follower_edit_event_count.borrow(), 0);
17127
17128    // Update the scroll position only
17129    _ = leader.update(cx, |leader, window, cx| {
17130        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17131    });
17132    follower
17133        .update(cx, |follower, window, cx| {
17134            follower.apply_update_proto(
17135                &project,
17136                pending_update.borrow_mut().take().unwrap(),
17137                window,
17138                cx,
17139            )
17140        })
17141        .unwrap()
17142        .await
17143        .unwrap();
17144    assert_eq!(
17145        follower
17146            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17147            .unwrap(),
17148        gpui::Point::new(1.5, 3.5)
17149    );
17150    assert!(*is_still_following.borrow());
17151    assert_eq!(*follower_edit_event_count.borrow(), 0);
17152
17153    // Update the selections and scroll position. The follower's scroll position is updated
17154    // via autoscroll, not via the leader's exact scroll position.
17155    _ = leader.update(cx, |leader, window, cx| {
17156        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17157            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17158        });
17159        leader.request_autoscroll(Autoscroll::newest(), cx);
17160        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17161    });
17162    follower
17163        .update(cx, |follower, window, cx| {
17164            follower.apply_update_proto(
17165                &project,
17166                pending_update.borrow_mut().take().unwrap(),
17167                window,
17168                cx,
17169            )
17170        })
17171        .unwrap()
17172        .await
17173        .unwrap();
17174    _ = follower.update(cx, |follower, _, cx| {
17175        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17176        assert_eq!(
17177            follower.selections.ranges(&follower.display_snapshot(cx)),
17178            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17179        );
17180    });
17181    assert!(*is_still_following.borrow());
17182
17183    // Creating a pending selection that precedes another selection
17184    _ = leader.update(cx, |leader, window, cx| {
17185        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17186            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17187        });
17188        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17189    });
17190    follower
17191        .update(cx, |follower, window, cx| {
17192            follower.apply_update_proto(
17193                &project,
17194                pending_update.borrow_mut().take().unwrap(),
17195                window,
17196                cx,
17197            )
17198        })
17199        .unwrap()
17200        .await
17201        .unwrap();
17202    _ = follower.update(cx, |follower, _, cx| {
17203        assert_eq!(
17204            follower.selections.ranges(&follower.display_snapshot(cx)),
17205            vec![
17206                MultiBufferOffset(0)..MultiBufferOffset(0),
17207                MultiBufferOffset(1)..MultiBufferOffset(1)
17208            ]
17209        );
17210    });
17211    assert!(*is_still_following.borrow());
17212
17213    // Extend the pending selection so that it surrounds another selection
17214    _ = leader.update(cx, |leader, window, cx| {
17215        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17216    });
17217    follower
17218        .update(cx, |follower, window, cx| {
17219            follower.apply_update_proto(
17220                &project,
17221                pending_update.borrow_mut().take().unwrap(),
17222                window,
17223                cx,
17224            )
17225        })
17226        .unwrap()
17227        .await
17228        .unwrap();
17229    _ = follower.update(cx, |follower, _, cx| {
17230        assert_eq!(
17231            follower.selections.ranges(&follower.display_snapshot(cx)),
17232            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17233        );
17234    });
17235
17236    // Scrolling locally breaks the follow
17237    _ = follower.update(cx, |follower, window, cx| {
17238        let top_anchor = follower
17239            .buffer()
17240            .read(cx)
17241            .read(cx)
17242            .anchor_after(MultiBufferOffset(0));
17243        follower.set_scroll_anchor(
17244            ScrollAnchor {
17245                anchor: top_anchor,
17246                offset: gpui::Point::new(0.0, 0.5),
17247            },
17248            window,
17249            cx,
17250        );
17251    });
17252    assert!(!(*is_still_following.borrow()));
17253}
17254
17255#[gpui::test]
17256async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17257    init_test(cx, |_| {});
17258
17259    let fs = FakeFs::new(cx.executor());
17260    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17261    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17262    let pane = workspace
17263        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17264        .unwrap();
17265
17266    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17267
17268    let leader = pane.update_in(cx, |_, window, cx| {
17269        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17270        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17271    });
17272
17273    // Start following the editor when it has no excerpts.
17274    let mut state_message =
17275        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17276    let workspace_entity = workspace.root(cx).unwrap();
17277    let follower_1 = cx
17278        .update_window(*workspace.deref(), |_, window, cx| {
17279            Editor::from_state_proto(
17280                workspace_entity,
17281                ViewId {
17282                    creator: CollaboratorId::PeerId(PeerId::default()),
17283                    id: 0,
17284                },
17285                &mut state_message,
17286                window,
17287                cx,
17288            )
17289        })
17290        .unwrap()
17291        .unwrap()
17292        .await
17293        .unwrap();
17294
17295    let update_message = Rc::new(RefCell::new(None));
17296    follower_1.update_in(cx, {
17297        let update = update_message.clone();
17298        |_, window, cx| {
17299            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17300                leader.read(cx).add_event_to_update_proto(
17301                    event,
17302                    &mut update.borrow_mut(),
17303                    window,
17304                    cx,
17305                );
17306            })
17307            .detach();
17308        }
17309    });
17310
17311    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17312        (
17313            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17314            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17315        )
17316    });
17317
17318    // Insert some excerpts.
17319    leader.update(cx, |leader, cx| {
17320        leader.buffer.update(cx, |multibuffer, cx| {
17321            multibuffer.set_excerpts_for_path(
17322                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17323                buffer_1.clone(),
17324                vec![
17325                    Point::row_range(0..3),
17326                    Point::row_range(1..6),
17327                    Point::row_range(12..15),
17328                ],
17329                0,
17330                cx,
17331            );
17332            multibuffer.set_excerpts_for_path(
17333                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17334                buffer_2.clone(),
17335                vec![Point::row_range(0..6), Point::row_range(8..12)],
17336                0,
17337                cx,
17338            );
17339        });
17340    });
17341
17342    // Apply the update of adding the excerpts.
17343    follower_1
17344        .update_in(cx, |follower, window, cx| {
17345            follower.apply_update_proto(
17346                &project,
17347                update_message.borrow().clone().unwrap(),
17348                window,
17349                cx,
17350            )
17351        })
17352        .await
17353        .unwrap();
17354    assert_eq!(
17355        follower_1.update(cx, |editor, cx| editor.text(cx)),
17356        leader.update(cx, |editor, cx| editor.text(cx))
17357    );
17358    update_message.borrow_mut().take();
17359
17360    // Start following separately after it already has excerpts.
17361    let mut state_message =
17362        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17363    let workspace_entity = workspace.root(cx).unwrap();
17364    let follower_2 = cx
17365        .update_window(*workspace.deref(), |_, window, cx| {
17366            Editor::from_state_proto(
17367                workspace_entity,
17368                ViewId {
17369                    creator: CollaboratorId::PeerId(PeerId::default()),
17370                    id: 0,
17371                },
17372                &mut state_message,
17373                window,
17374                cx,
17375            )
17376        })
17377        .unwrap()
17378        .unwrap()
17379        .await
17380        .unwrap();
17381    assert_eq!(
17382        follower_2.update(cx, |editor, cx| editor.text(cx)),
17383        leader.update(cx, |editor, cx| editor.text(cx))
17384    );
17385
17386    // Remove some excerpts.
17387    leader.update(cx, |leader, cx| {
17388        leader.buffer.update(cx, |multibuffer, cx| {
17389            let excerpt_ids = multibuffer.excerpt_ids();
17390            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17391            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17392        });
17393    });
17394
17395    // Apply the update of removing the excerpts.
17396    follower_1
17397        .update_in(cx, |follower, window, cx| {
17398            follower.apply_update_proto(
17399                &project,
17400                update_message.borrow().clone().unwrap(),
17401                window,
17402                cx,
17403            )
17404        })
17405        .await
17406        .unwrap();
17407    follower_2
17408        .update_in(cx, |follower, window, cx| {
17409            follower.apply_update_proto(
17410                &project,
17411                update_message.borrow().clone().unwrap(),
17412                window,
17413                cx,
17414            )
17415        })
17416        .await
17417        .unwrap();
17418    update_message.borrow_mut().take();
17419    assert_eq!(
17420        follower_1.update(cx, |editor, cx| editor.text(cx)),
17421        leader.update(cx, |editor, cx| editor.text(cx))
17422    );
17423}
17424
17425#[gpui::test]
17426async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17427    init_test(cx, |_| {});
17428
17429    let mut cx = EditorTestContext::new(cx).await;
17430    let lsp_store =
17431        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17432
17433    cx.set_state(indoc! {"
17434        ˇfn func(abc def: i32) -> u32 {
17435        }
17436    "});
17437
17438    cx.update(|_, cx| {
17439        lsp_store.update(cx, |lsp_store, cx| {
17440            lsp_store
17441                .update_diagnostics(
17442                    LanguageServerId(0),
17443                    lsp::PublishDiagnosticsParams {
17444                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17445                        version: None,
17446                        diagnostics: vec![
17447                            lsp::Diagnostic {
17448                                range: lsp::Range::new(
17449                                    lsp::Position::new(0, 11),
17450                                    lsp::Position::new(0, 12),
17451                                ),
17452                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17453                                ..Default::default()
17454                            },
17455                            lsp::Diagnostic {
17456                                range: lsp::Range::new(
17457                                    lsp::Position::new(0, 12),
17458                                    lsp::Position::new(0, 15),
17459                                ),
17460                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17461                                ..Default::default()
17462                            },
17463                            lsp::Diagnostic {
17464                                range: lsp::Range::new(
17465                                    lsp::Position::new(0, 25),
17466                                    lsp::Position::new(0, 28),
17467                                ),
17468                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17469                                ..Default::default()
17470                            },
17471                        ],
17472                    },
17473                    None,
17474                    DiagnosticSourceKind::Pushed,
17475                    &[],
17476                    cx,
17477                )
17478                .unwrap()
17479        });
17480    });
17481
17482    executor.run_until_parked();
17483
17484    cx.update_editor(|editor, window, cx| {
17485        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17486    });
17487
17488    cx.assert_editor_state(indoc! {"
17489        fn func(abc def: i32) -> ˇu32 {
17490        }
17491    "});
17492
17493    cx.update_editor(|editor, window, cx| {
17494        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17495    });
17496
17497    cx.assert_editor_state(indoc! {"
17498        fn func(abc ˇdef: i32) -> u32 {
17499        }
17500    "});
17501
17502    cx.update_editor(|editor, window, cx| {
17503        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17504    });
17505
17506    cx.assert_editor_state(indoc! {"
17507        fn func(abcˇ def: i32) -> u32 {
17508        }
17509    "});
17510
17511    cx.update_editor(|editor, window, cx| {
17512        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17513    });
17514
17515    cx.assert_editor_state(indoc! {"
17516        fn func(abc def: i32) -> ˇu32 {
17517        }
17518    "});
17519}
17520
17521#[gpui::test]
17522async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17523    init_test(cx, |_| {});
17524
17525    let mut cx = EditorTestContext::new(cx).await;
17526
17527    let diff_base = r#"
17528        use some::mod;
17529
17530        const A: u32 = 42;
17531
17532        fn main() {
17533            println!("hello");
17534
17535            println!("world");
17536        }
17537        "#
17538    .unindent();
17539
17540    // Edits are modified, removed, modified, added
17541    cx.set_state(
17542        &r#"
17543        use some::modified;
17544
17545        ˇ
17546        fn main() {
17547            println!("hello there");
17548
17549            println!("around the");
17550            println!("world");
17551        }
17552        "#
17553        .unindent(),
17554    );
17555
17556    cx.set_head_text(&diff_base);
17557    executor.run_until_parked();
17558
17559    cx.update_editor(|editor, window, cx| {
17560        //Wrap around the bottom of the buffer
17561        for _ in 0..3 {
17562            editor.go_to_next_hunk(&GoToHunk, window, cx);
17563        }
17564    });
17565
17566    cx.assert_editor_state(
17567        &r#"
17568        ˇuse some::modified;
17569
17570
17571        fn main() {
17572            println!("hello there");
17573
17574            println!("around the");
17575            println!("world");
17576        }
17577        "#
17578        .unindent(),
17579    );
17580
17581    cx.update_editor(|editor, window, cx| {
17582        //Wrap around the top of the buffer
17583        for _ in 0..2 {
17584            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17585        }
17586    });
17587
17588    cx.assert_editor_state(
17589        &r#"
17590        use some::modified;
17591
17592
17593        fn main() {
17594        ˇ    println!("hello there");
17595
17596            println!("around the");
17597            println!("world");
17598        }
17599        "#
17600        .unindent(),
17601    );
17602
17603    cx.update_editor(|editor, window, cx| {
17604        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17605    });
17606
17607    cx.assert_editor_state(
17608        &r#"
17609        use some::modified;
17610
17611        ˇ
17612        fn main() {
17613            println!("hello there");
17614
17615            println!("around the");
17616            println!("world");
17617        }
17618        "#
17619        .unindent(),
17620    );
17621
17622    cx.update_editor(|editor, window, cx| {
17623        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17624    });
17625
17626    cx.assert_editor_state(
17627        &r#"
17628        ˇuse some::modified;
17629
17630
17631        fn main() {
17632            println!("hello there");
17633
17634            println!("around the");
17635            println!("world");
17636        }
17637        "#
17638        .unindent(),
17639    );
17640
17641    cx.update_editor(|editor, window, cx| {
17642        for _ in 0..2 {
17643            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17644        }
17645    });
17646
17647    cx.assert_editor_state(
17648        &r#"
17649        use some::modified;
17650
17651
17652        fn main() {
17653        ˇ    println!("hello there");
17654
17655            println!("around the");
17656            println!("world");
17657        }
17658        "#
17659        .unindent(),
17660    );
17661
17662    cx.update_editor(|editor, window, cx| {
17663        editor.fold(&Fold, window, cx);
17664    });
17665
17666    cx.update_editor(|editor, window, cx| {
17667        editor.go_to_next_hunk(&GoToHunk, window, cx);
17668    });
17669
17670    cx.assert_editor_state(
17671        &r#"
17672        ˇuse some::modified;
17673
17674
17675        fn main() {
17676            println!("hello there");
17677
17678            println!("around the");
17679            println!("world");
17680        }
17681        "#
17682        .unindent(),
17683    );
17684}
17685
17686#[test]
17687fn test_split_words() {
17688    fn split(text: &str) -> Vec<&str> {
17689        split_words(text).collect()
17690    }
17691
17692    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17693    assert_eq!(split("hello_world"), &["hello_", "world"]);
17694    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17695    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17696    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17697    assert_eq!(split("helloworld"), &["helloworld"]);
17698
17699    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17700}
17701
17702#[test]
17703fn test_split_words_for_snippet_prefix() {
17704    fn split(text: &str) -> Vec<&str> {
17705        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
17706    }
17707
17708    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17709    assert_eq!(split("hello_world"), &["hello_world"]);
17710    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17711    assert_eq!(split("Hello_World"), &["Hello_World"]);
17712    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17713    assert_eq!(split("helloworld"), &["helloworld"]);
17714    assert_eq!(
17715        split("this@is!@#$^many   . symbols"),
17716        &[
17717            "symbols",
17718            " symbols",
17719            ". symbols",
17720            " . symbols",
17721            "  . symbols",
17722            "   . symbols",
17723            "many   . symbols",
17724            "^many   . symbols",
17725            "$^many   . symbols",
17726            "#$^many   . symbols",
17727            "@#$^many   . symbols",
17728            "!@#$^many   . symbols",
17729            "is!@#$^many   . symbols",
17730            "@is!@#$^many   . symbols",
17731            "this@is!@#$^many   . symbols",
17732        ],
17733    );
17734    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
17735}
17736
17737#[gpui::test]
17738async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17739    init_test(cx, |_| {});
17740
17741    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17742
17743    #[track_caller]
17744    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
17745        let _state_context = cx.set_state(before);
17746        cx.run_until_parked();
17747        cx.update_editor(|editor, window, cx| {
17748            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17749        });
17750        cx.run_until_parked();
17751        cx.assert_editor_state(after);
17752    }
17753
17754    // Outside bracket jumps to outside of matching bracket
17755    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
17756    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
17757
17758    // Inside bracket jumps to inside of matching bracket
17759    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
17760    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
17761
17762    // When outside a bracket and inside, favor jumping to the inside bracket
17763    assert(
17764        "console.log('foo', [1, 2, 3]ˇ);",
17765        "console.log('foo', ˇ[1, 2, 3]);",
17766        &mut cx,
17767    );
17768    assert(
17769        "console.log(ˇ'foo', [1, 2, 3]);",
17770        "console.log('foo'ˇ, [1, 2, 3]);",
17771        &mut cx,
17772    );
17773
17774    // Bias forward if two options are equally likely
17775    assert(
17776        "let result = curried_fun()ˇ();",
17777        "let result = curried_fun()()ˇ;",
17778        &mut cx,
17779    );
17780
17781    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17782    assert(
17783        indoc! {"
17784            function test() {
17785                console.log('test')ˇ
17786            }"},
17787        indoc! {"
17788            function test() {
17789                console.logˇ('test')
17790            }"},
17791        &mut cx,
17792    );
17793}
17794
17795#[gpui::test]
17796async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
17797    init_test(cx, |_| {});
17798    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
17799    language_registry.add(markdown_lang());
17800    language_registry.add(rust_lang());
17801    let buffer = cx.new(|cx| {
17802        let mut buffer = language::Buffer::local(
17803            indoc! {"
17804            ```rs
17805            impl Worktree {
17806                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17807                }
17808            }
17809            ```
17810        "},
17811            cx,
17812        );
17813        buffer.set_language_registry(language_registry.clone());
17814        buffer.set_language(Some(markdown_lang()), cx);
17815        buffer
17816    });
17817    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17818    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17819    cx.executor().run_until_parked();
17820    _ = editor.update(cx, |editor, window, cx| {
17821        // Case 1: Test outer enclosing brackets
17822        select_ranges(
17823            editor,
17824            &indoc! {"
17825                ```rs
17826                impl Worktree {
17827                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17828                    }
1782917830                ```
17831            "},
17832            window,
17833            cx,
17834        );
17835        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17836        assert_text_with_selections(
17837            editor,
17838            &indoc! {"
17839                ```rs
17840                impl Worktree ˇ{
17841                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17842                    }
17843                }
17844                ```
17845            "},
17846            cx,
17847        );
17848        // Case 2: Test inner enclosing brackets
17849        select_ranges(
17850            editor,
17851            &indoc! {"
17852                ```rs
17853                impl Worktree {
17854                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1785517856                }
17857                ```
17858            "},
17859            window,
17860            cx,
17861        );
17862        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17863        assert_text_with_selections(
17864            editor,
17865            &indoc! {"
17866                ```rs
17867                impl Worktree {
17868                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
17869                    }
17870                }
17871                ```
17872            "},
17873            cx,
17874        );
17875    });
17876}
17877
17878#[gpui::test]
17879async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17880    init_test(cx, |_| {});
17881
17882    let fs = FakeFs::new(cx.executor());
17883    fs.insert_tree(
17884        path!("/a"),
17885        json!({
17886            "main.rs": "fn main() { let a = 5; }",
17887            "other.rs": "// Test file",
17888        }),
17889    )
17890    .await;
17891    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17892
17893    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17894    language_registry.add(Arc::new(Language::new(
17895        LanguageConfig {
17896            name: "Rust".into(),
17897            matcher: LanguageMatcher {
17898                path_suffixes: vec!["rs".to_string()],
17899                ..Default::default()
17900            },
17901            brackets: BracketPairConfig {
17902                pairs: vec![BracketPair {
17903                    start: "{".to_string(),
17904                    end: "}".to_string(),
17905                    close: true,
17906                    surround: true,
17907                    newline: true,
17908                }],
17909                disabled_scopes_by_bracket_ix: Vec::new(),
17910            },
17911            ..Default::default()
17912        },
17913        Some(tree_sitter_rust::LANGUAGE.into()),
17914    )));
17915    let mut fake_servers = language_registry.register_fake_lsp(
17916        "Rust",
17917        FakeLspAdapter {
17918            capabilities: lsp::ServerCapabilities {
17919                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17920                    first_trigger_character: "{".to_string(),
17921                    more_trigger_character: None,
17922                }),
17923                ..Default::default()
17924            },
17925            ..Default::default()
17926        },
17927    );
17928
17929    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17930
17931    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17932
17933    let worktree_id = workspace
17934        .update(cx, |workspace, _, cx| {
17935            workspace.project().update(cx, |project, cx| {
17936                project.worktrees(cx).next().unwrap().read(cx).id()
17937            })
17938        })
17939        .unwrap();
17940
17941    let buffer = project
17942        .update(cx, |project, cx| {
17943            project.open_local_buffer(path!("/a/main.rs"), cx)
17944        })
17945        .await
17946        .unwrap();
17947    let editor_handle = workspace
17948        .update(cx, |workspace, window, cx| {
17949            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17950        })
17951        .unwrap()
17952        .await
17953        .unwrap()
17954        .downcast::<Editor>()
17955        .unwrap();
17956
17957    cx.executor().start_waiting();
17958    let fake_server = fake_servers.next().await.unwrap();
17959
17960    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17961        |params, _| async move {
17962            assert_eq!(
17963                params.text_document_position.text_document.uri,
17964                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17965            );
17966            assert_eq!(
17967                params.text_document_position.position,
17968                lsp::Position::new(0, 21),
17969            );
17970
17971            Ok(Some(vec![lsp::TextEdit {
17972                new_text: "]".to_string(),
17973                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17974            }]))
17975        },
17976    );
17977
17978    editor_handle.update_in(cx, |editor, window, cx| {
17979        window.focus(&editor.focus_handle(cx));
17980        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17981            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17982        });
17983        editor.handle_input("{", window, cx);
17984    });
17985
17986    cx.executor().run_until_parked();
17987
17988    buffer.update(cx, |buffer, _| {
17989        assert_eq!(
17990            buffer.text(),
17991            "fn main() { let a = {5}; }",
17992            "No extra braces from on type formatting should appear in the buffer"
17993        )
17994    });
17995}
17996
17997#[gpui::test(iterations = 20, seeds(31))]
17998async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17999    init_test(cx, |_| {});
18000
18001    let mut cx = EditorLspTestContext::new_rust(
18002        lsp::ServerCapabilities {
18003            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18004                first_trigger_character: ".".to_string(),
18005                more_trigger_character: None,
18006            }),
18007            ..Default::default()
18008        },
18009        cx,
18010    )
18011    .await;
18012
18013    cx.update_buffer(|buffer, _| {
18014        // This causes autoindent to be async.
18015        buffer.set_sync_parse_timeout(Duration::ZERO)
18016    });
18017
18018    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18019    cx.simulate_keystroke("\n");
18020    cx.run_until_parked();
18021
18022    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18023    let mut request =
18024        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18025            let buffer_cloned = buffer_cloned.clone();
18026            async move {
18027                buffer_cloned.update(&mut cx, |buffer, _| {
18028                    assert_eq!(
18029                        buffer.text(),
18030                        "fn c() {\n    d()\n        .\n}\n",
18031                        "OnTypeFormatting should triggered after autoindent applied"
18032                    )
18033                })?;
18034
18035                Ok(Some(vec![]))
18036            }
18037        });
18038
18039    cx.simulate_keystroke(".");
18040    cx.run_until_parked();
18041
18042    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18043    assert!(request.next().await.is_some());
18044    request.close();
18045    assert!(request.next().await.is_none());
18046}
18047
18048#[gpui::test]
18049async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18050    init_test(cx, |_| {});
18051
18052    let fs = FakeFs::new(cx.executor());
18053    fs.insert_tree(
18054        path!("/a"),
18055        json!({
18056            "main.rs": "fn main() { let a = 5; }",
18057            "other.rs": "// Test file",
18058        }),
18059    )
18060    .await;
18061
18062    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18063
18064    let server_restarts = Arc::new(AtomicUsize::new(0));
18065    let closure_restarts = Arc::clone(&server_restarts);
18066    let language_server_name = "test language server";
18067    let language_name: LanguageName = "Rust".into();
18068
18069    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18070    language_registry.add(Arc::new(Language::new(
18071        LanguageConfig {
18072            name: language_name.clone(),
18073            matcher: LanguageMatcher {
18074                path_suffixes: vec!["rs".to_string()],
18075                ..Default::default()
18076            },
18077            ..Default::default()
18078        },
18079        Some(tree_sitter_rust::LANGUAGE.into()),
18080    )));
18081    let mut fake_servers = language_registry.register_fake_lsp(
18082        "Rust",
18083        FakeLspAdapter {
18084            name: language_server_name,
18085            initialization_options: Some(json!({
18086                "testOptionValue": true
18087            })),
18088            initializer: Some(Box::new(move |fake_server| {
18089                let task_restarts = Arc::clone(&closure_restarts);
18090                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18091                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18092                    futures::future::ready(Ok(()))
18093                });
18094            })),
18095            ..Default::default()
18096        },
18097    );
18098
18099    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18100    let _buffer = project
18101        .update(cx, |project, cx| {
18102            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18103        })
18104        .await
18105        .unwrap();
18106    let _fake_server = fake_servers.next().await.unwrap();
18107    update_test_language_settings(cx, |language_settings| {
18108        language_settings.languages.0.insert(
18109            language_name.clone().0,
18110            LanguageSettingsContent {
18111                tab_size: NonZeroU32::new(8),
18112                ..Default::default()
18113            },
18114        );
18115    });
18116    cx.executor().run_until_parked();
18117    assert_eq!(
18118        server_restarts.load(atomic::Ordering::Acquire),
18119        0,
18120        "Should not restart LSP server on an unrelated change"
18121    );
18122
18123    update_test_project_settings(cx, |project_settings| {
18124        project_settings.lsp.insert(
18125            "Some other server name".into(),
18126            LspSettings {
18127                binary: None,
18128                settings: None,
18129                initialization_options: Some(json!({
18130                    "some other init value": false
18131                })),
18132                enable_lsp_tasks: false,
18133                fetch: None,
18134            },
18135        );
18136    });
18137    cx.executor().run_until_parked();
18138    assert_eq!(
18139        server_restarts.load(atomic::Ordering::Acquire),
18140        0,
18141        "Should not restart LSP server on an unrelated LSP settings change"
18142    );
18143
18144    update_test_project_settings(cx, |project_settings| {
18145        project_settings.lsp.insert(
18146            language_server_name.into(),
18147            LspSettings {
18148                binary: None,
18149                settings: None,
18150                initialization_options: Some(json!({
18151                    "anotherInitValue": false
18152                })),
18153                enable_lsp_tasks: false,
18154                fetch: None,
18155            },
18156        );
18157    });
18158    cx.executor().run_until_parked();
18159    assert_eq!(
18160        server_restarts.load(atomic::Ordering::Acquire),
18161        1,
18162        "Should restart LSP server on a related LSP settings change"
18163    );
18164
18165    update_test_project_settings(cx, |project_settings| {
18166        project_settings.lsp.insert(
18167            language_server_name.into(),
18168            LspSettings {
18169                binary: None,
18170                settings: None,
18171                initialization_options: Some(json!({
18172                    "anotherInitValue": false
18173                })),
18174                enable_lsp_tasks: false,
18175                fetch: None,
18176            },
18177        );
18178    });
18179    cx.executor().run_until_parked();
18180    assert_eq!(
18181        server_restarts.load(atomic::Ordering::Acquire),
18182        1,
18183        "Should not restart LSP server on a related LSP settings change that is the same"
18184    );
18185
18186    update_test_project_settings(cx, |project_settings| {
18187        project_settings.lsp.insert(
18188            language_server_name.into(),
18189            LspSettings {
18190                binary: None,
18191                settings: None,
18192                initialization_options: None,
18193                enable_lsp_tasks: false,
18194                fetch: None,
18195            },
18196        );
18197    });
18198    cx.executor().run_until_parked();
18199    assert_eq!(
18200        server_restarts.load(atomic::Ordering::Acquire),
18201        2,
18202        "Should restart LSP server on another related LSP settings change"
18203    );
18204}
18205
18206#[gpui::test]
18207async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18208    init_test(cx, |_| {});
18209
18210    let mut cx = EditorLspTestContext::new_rust(
18211        lsp::ServerCapabilities {
18212            completion_provider: Some(lsp::CompletionOptions {
18213                trigger_characters: Some(vec![".".to_string()]),
18214                resolve_provider: Some(true),
18215                ..Default::default()
18216            }),
18217            ..Default::default()
18218        },
18219        cx,
18220    )
18221    .await;
18222
18223    cx.set_state("fn main() { let a = 2ˇ; }");
18224    cx.simulate_keystroke(".");
18225    let completion_item = lsp::CompletionItem {
18226        label: "some".into(),
18227        kind: Some(lsp::CompletionItemKind::SNIPPET),
18228        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18229        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18230            kind: lsp::MarkupKind::Markdown,
18231            value: "```rust\nSome(2)\n```".to_string(),
18232        })),
18233        deprecated: Some(false),
18234        sort_text: Some("fffffff2".to_string()),
18235        filter_text: Some("some".to_string()),
18236        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18237        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18238            range: lsp::Range {
18239                start: lsp::Position {
18240                    line: 0,
18241                    character: 22,
18242                },
18243                end: lsp::Position {
18244                    line: 0,
18245                    character: 22,
18246                },
18247            },
18248            new_text: "Some(2)".to_string(),
18249        })),
18250        additional_text_edits: Some(vec![lsp::TextEdit {
18251            range: lsp::Range {
18252                start: lsp::Position {
18253                    line: 0,
18254                    character: 20,
18255                },
18256                end: lsp::Position {
18257                    line: 0,
18258                    character: 22,
18259                },
18260            },
18261            new_text: "".to_string(),
18262        }]),
18263        ..Default::default()
18264    };
18265
18266    let closure_completion_item = completion_item.clone();
18267    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18268        let task_completion_item = closure_completion_item.clone();
18269        async move {
18270            Ok(Some(lsp::CompletionResponse::Array(vec![
18271                task_completion_item,
18272            ])))
18273        }
18274    });
18275
18276    request.next().await;
18277
18278    cx.condition(|editor, _| editor.context_menu_visible())
18279        .await;
18280    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18281        editor
18282            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18283            .unwrap()
18284    });
18285    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18286
18287    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18288        let task_completion_item = completion_item.clone();
18289        async move { Ok(task_completion_item) }
18290    })
18291    .next()
18292    .await
18293    .unwrap();
18294    apply_additional_edits.await.unwrap();
18295    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18296}
18297
18298#[gpui::test]
18299async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18300    init_test(cx, |_| {});
18301
18302    let mut cx = EditorLspTestContext::new_rust(
18303        lsp::ServerCapabilities {
18304            completion_provider: Some(lsp::CompletionOptions {
18305                trigger_characters: Some(vec![".".to_string()]),
18306                resolve_provider: Some(true),
18307                ..Default::default()
18308            }),
18309            ..Default::default()
18310        },
18311        cx,
18312    )
18313    .await;
18314
18315    cx.set_state("fn main() { let a = 2ˇ; }");
18316    cx.simulate_keystroke(".");
18317
18318    let item1 = lsp::CompletionItem {
18319        label: "method id()".to_string(),
18320        filter_text: Some("id".to_string()),
18321        detail: None,
18322        documentation: None,
18323        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18324            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18325            new_text: ".id".to_string(),
18326        })),
18327        ..lsp::CompletionItem::default()
18328    };
18329
18330    let item2 = lsp::CompletionItem {
18331        label: "other".to_string(),
18332        filter_text: Some("other".to_string()),
18333        detail: None,
18334        documentation: None,
18335        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18336            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18337            new_text: ".other".to_string(),
18338        })),
18339        ..lsp::CompletionItem::default()
18340    };
18341
18342    let item1 = item1.clone();
18343    cx.set_request_handler::<lsp::request::Completion, _, _>({
18344        let item1 = item1.clone();
18345        move |_, _, _| {
18346            let item1 = item1.clone();
18347            let item2 = item2.clone();
18348            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18349        }
18350    })
18351    .next()
18352    .await;
18353
18354    cx.condition(|editor, _| editor.context_menu_visible())
18355        .await;
18356    cx.update_editor(|editor, _, _| {
18357        let context_menu = editor.context_menu.borrow_mut();
18358        let context_menu = context_menu
18359            .as_ref()
18360            .expect("Should have the context menu deployed");
18361        match context_menu {
18362            CodeContextMenu::Completions(completions_menu) => {
18363                let completions = completions_menu.completions.borrow_mut();
18364                assert_eq!(
18365                    completions
18366                        .iter()
18367                        .map(|completion| &completion.label.text)
18368                        .collect::<Vec<_>>(),
18369                    vec!["method id()", "other"]
18370                )
18371            }
18372            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18373        }
18374    });
18375
18376    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18377        let item1 = item1.clone();
18378        move |_, item_to_resolve, _| {
18379            let item1 = item1.clone();
18380            async move {
18381                if item1 == item_to_resolve {
18382                    Ok(lsp::CompletionItem {
18383                        label: "method id()".to_string(),
18384                        filter_text: Some("id".to_string()),
18385                        detail: Some("Now resolved!".to_string()),
18386                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18387                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18388                            range: lsp::Range::new(
18389                                lsp::Position::new(0, 22),
18390                                lsp::Position::new(0, 22),
18391                            ),
18392                            new_text: ".id".to_string(),
18393                        })),
18394                        ..lsp::CompletionItem::default()
18395                    })
18396                } else {
18397                    Ok(item_to_resolve)
18398                }
18399            }
18400        }
18401    })
18402    .next()
18403    .await
18404    .unwrap();
18405    cx.run_until_parked();
18406
18407    cx.update_editor(|editor, window, cx| {
18408        editor.context_menu_next(&Default::default(), window, cx);
18409    });
18410
18411    cx.update_editor(|editor, _, _| {
18412        let context_menu = editor.context_menu.borrow_mut();
18413        let context_menu = context_menu
18414            .as_ref()
18415            .expect("Should have the context menu deployed");
18416        match context_menu {
18417            CodeContextMenu::Completions(completions_menu) => {
18418                let completions = completions_menu.completions.borrow_mut();
18419                assert_eq!(
18420                    completions
18421                        .iter()
18422                        .map(|completion| &completion.label.text)
18423                        .collect::<Vec<_>>(),
18424                    vec!["method id() Now resolved!", "other"],
18425                    "Should update first completion label, but not second as the filter text did not match."
18426                );
18427            }
18428            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18429        }
18430    });
18431}
18432
18433#[gpui::test]
18434async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18435    init_test(cx, |_| {});
18436    let mut cx = EditorLspTestContext::new_rust(
18437        lsp::ServerCapabilities {
18438            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18439            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18440            completion_provider: Some(lsp::CompletionOptions {
18441                resolve_provider: Some(true),
18442                ..Default::default()
18443            }),
18444            ..Default::default()
18445        },
18446        cx,
18447    )
18448    .await;
18449    cx.set_state(indoc! {"
18450        struct TestStruct {
18451            field: i32
18452        }
18453
18454        fn mainˇ() {
18455            let unused_var = 42;
18456            let test_struct = TestStruct { field: 42 };
18457        }
18458    "});
18459    let symbol_range = cx.lsp_range(indoc! {"
18460        struct TestStruct {
18461            field: i32
18462        }
18463
18464        «fn main»() {
18465            let unused_var = 42;
18466            let test_struct = TestStruct { field: 42 };
18467        }
18468    "});
18469    let mut hover_requests =
18470        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18471            Ok(Some(lsp::Hover {
18472                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18473                    kind: lsp::MarkupKind::Markdown,
18474                    value: "Function documentation".to_string(),
18475                }),
18476                range: Some(symbol_range),
18477            }))
18478        });
18479
18480    // Case 1: Test that code action menu hide hover popover
18481    cx.dispatch_action(Hover);
18482    hover_requests.next().await;
18483    cx.condition(|editor, _| editor.hover_state.visible()).await;
18484    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18485        move |_, _, _| async move {
18486            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18487                lsp::CodeAction {
18488                    title: "Remove unused variable".to_string(),
18489                    kind: Some(CodeActionKind::QUICKFIX),
18490                    edit: Some(lsp::WorkspaceEdit {
18491                        changes: Some(
18492                            [(
18493                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18494                                vec![lsp::TextEdit {
18495                                    range: lsp::Range::new(
18496                                        lsp::Position::new(5, 4),
18497                                        lsp::Position::new(5, 27),
18498                                    ),
18499                                    new_text: "".to_string(),
18500                                }],
18501                            )]
18502                            .into_iter()
18503                            .collect(),
18504                        ),
18505                        ..Default::default()
18506                    }),
18507                    ..Default::default()
18508                },
18509            )]))
18510        },
18511    );
18512    cx.update_editor(|editor, window, cx| {
18513        editor.toggle_code_actions(
18514            &ToggleCodeActions {
18515                deployed_from: None,
18516                quick_launch: false,
18517            },
18518            window,
18519            cx,
18520        );
18521    });
18522    code_action_requests.next().await;
18523    cx.run_until_parked();
18524    cx.condition(|editor, _| editor.context_menu_visible())
18525        .await;
18526    cx.update_editor(|editor, _, _| {
18527        assert!(
18528            !editor.hover_state.visible(),
18529            "Hover popover should be hidden when code action menu is shown"
18530        );
18531        // Hide code actions
18532        editor.context_menu.take();
18533    });
18534
18535    // Case 2: Test that code completions hide hover popover
18536    cx.dispatch_action(Hover);
18537    hover_requests.next().await;
18538    cx.condition(|editor, _| editor.hover_state.visible()).await;
18539    let counter = Arc::new(AtomicUsize::new(0));
18540    let mut completion_requests =
18541        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18542            let counter = counter.clone();
18543            async move {
18544                counter.fetch_add(1, atomic::Ordering::Release);
18545                Ok(Some(lsp::CompletionResponse::Array(vec![
18546                    lsp::CompletionItem {
18547                        label: "main".into(),
18548                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18549                        detail: Some("() -> ()".to_string()),
18550                        ..Default::default()
18551                    },
18552                    lsp::CompletionItem {
18553                        label: "TestStruct".into(),
18554                        kind: Some(lsp::CompletionItemKind::STRUCT),
18555                        detail: Some("struct TestStruct".to_string()),
18556                        ..Default::default()
18557                    },
18558                ])))
18559            }
18560        });
18561    cx.update_editor(|editor, window, cx| {
18562        editor.show_completions(&ShowCompletions, window, cx);
18563    });
18564    completion_requests.next().await;
18565    cx.condition(|editor, _| editor.context_menu_visible())
18566        .await;
18567    cx.update_editor(|editor, _, _| {
18568        assert!(
18569            !editor.hover_state.visible(),
18570            "Hover popover should be hidden when completion menu is shown"
18571        );
18572    });
18573}
18574
18575#[gpui::test]
18576async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18577    init_test(cx, |_| {});
18578
18579    let mut cx = EditorLspTestContext::new_rust(
18580        lsp::ServerCapabilities {
18581            completion_provider: Some(lsp::CompletionOptions {
18582                trigger_characters: Some(vec![".".to_string()]),
18583                resolve_provider: Some(true),
18584                ..Default::default()
18585            }),
18586            ..Default::default()
18587        },
18588        cx,
18589    )
18590    .await;
18591
18592    cx.set_state("fn main() { let a = 2ˇ; }");
18593    cx.simulate_keystroke(".");
18594
18595    let unresolved_item_1 = lsp::CompletionItem {
18596        label: "id".to_string(),
18597        filter_text: Some("id".to_string()),
18598        detail: None,
18599        documentation: None,
18600        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18601            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18602            new_text: ".id".to_string(),
18603        })),
18604        ..lsp::CompletionItem::default()
18605    };
18606    let resolved_item_1 = lsp::CompletionItem {
18607        additional_text_edits: Some(vec![lsp::TextEdit {
18608            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18609            new_text: "!!".to_string(),
18610        }]),
18611        ..unresolved_item_1.clone()
18612    };
18613    let unresolved_item_2 = lsp::CompletionItem {
18614        label: "other".to_string(),
18615        filter_text: Some("other".to_string()),
18616        detail: None,
18617        documentation: None,
18618        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18619            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18620            new_text: ".other".to_string(),
18621        })),
18622        ..lsp::CompletionItem::default()
18623    };
18624    let resolved_item_2 = lsp::CompletionItem {
18625        additional_text_edits: Some(vec![lsp::TextEdit {
18626            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18627            new_text: "??".to_string(),
18628        }]),
18629        ..unresolved_item_2.clone()
18630    };
18631
18632    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18633    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18634    cx.lsp
18635        .server
18636        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18637            let unresolved_item_1 = unresolved_item_1.clone();
18638            let resolved_item_1 = resolved_item_1.clone();
18639            let unresolved_item_2 = unresolved_item_2.clone();
18640            let resolved_item_2 = resolved_item_2.clone();
18641            let resolve_requests_1 = resolve_requests_1.clone();
18642            let resolve_requests_2 = resolve_requests_2.clone();
18643            move |unresolved_request, _| {
18644                let unresolved_item_1 = unresolved_item_1.clone();
18645                let resolved_item_1 = resolved_item_1.clone();
18646                let unresolved_item_2 = unresolved_item_2.clone();
18647                let resolved_item_2 = resolved_item_2.clone();
18648                let resolve_requests_1 = resolve_requests_1.clone();
18649                let resolve_requests_2 = resolve_requests_2.clone();
18650                async move {
18651                    if unresolved_request == unresolved_item_1 {
18652                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18653                        Ok(resolved_item_1.clone())
18654                    } else if unresolved_request == unresolved_item_2 {
18655                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18656                        Ok(resolved_item_2.clone())
18657                    } else {
18658                        panic!("Unexpected completion item {unresolved_request:?}")
18659                    }
18660                }
18661            }
18662        })
18663        .detach();
18664
18665    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18666        let unresolved_item_1 = unresolved_item_1.clone();
18667        let unresolved_item_2 = unresolved_item_2.clone();
18668        async move {
18669            Ok(Some(lsp::CompletionResponse::Array(vec![
18670                unresolved_item_1,
18671                unresolved_item_2,
18672            ])))
18673        }
18674    })
18675    .next()
18676    .await;
18677
18678    cx.condition(|editor, _| editor.context_menu_visible())
18679        .await;
18680    cx.update_editor(|editor, _, _| {
18681        let context_menu = editor.context_menu.borrow_mut();
18682        let context_menu = context_menu
18683            .as_ref()
18684            .expect("Should have the context menu deployed");
18685        match context_menu {
18686            CodeContextMenu::Completions(completions_menu) => {
18687                let completions = completions_menu.completions.borrow_mut();
18688                assert_eq!(
18689                    completions
18690                        .iter()
18691                        .map(|completion| &completion.label.text)
18692                        .collect::<Vec<_>>(),
18693                    vec!["id", "other"]
18694                )
18695            }
18696            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18697        }
18698    });
18699    cx.run_until_parked();
18700
18701    cx.update_editor(|editor, window, cx| {
18702        editor.context_menu_next(&ContextMenuNext, window, cx);
18703    });
18704    cx.run_until_parked();
18705    cx.update_editor(|editor, window, cx| {
18706        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18707    });
18708    cx.run_until_parked();
18709    cx.update_editor(|editor, window, cx| {
18710        editor.context_menu_next(&ContextMenuNext, window, cx);
18711    });
18712    cx.run_until_parked();
18713    cx.update_editor(|editor, window, cx| {
18714        editor
18715            .compose_completion(&ComposeCompletion::default(), window, cx)
18716            .expect("No task returned")
18717    })
18718    .await
18719    .expect("Completion failed");
18720    cx.run_until_parked();
18721
18722    cx.update_editor(|editor, _, cx| {
18723        assert_eq!(
18724            resolve_requests_1.load(atomic::Ordering::Acquire),
18725            1,
18726            "Should always resolve once despite multiple selections"
18727        );
18728        assert_eq!(
18729            resolve_requests_2.load(atomic::Ordering::Acquire),
18730            1,
18731            "Should always resolve once after multiple selections and applying the completion"
18732        );
18733        assert_eq!(
18734            editor.text(cx),
18735            "fn main() { let a = ??.other; }",
18736            "Should use resolved data when applying the completion"
18737        );
18738    });
18739}
18740
18741#[gpui::test]
18742async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18743    init_test(cx, |_| {});
18744
18745    let item_0 = lsp::CompletionItem {
18746        label: "abs".into(),
18747        insert_text: Some("abs".into()),
18748        data: Some(json!({ "very": "special"})),
18749        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18750        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18751            lsp::InsertReplaceEdit {
18752                new_text: "abs".to_string(),
18753                insert: lsp::Range::default(),
18754                replace: lsp::Range::default(),
18755            },
18756        )),
18757        ..lsp::CompletionItem::default()
18758    };
18759    let items = iter::once(item_0.clone())
18760        .chain((11..51).map(|i| lsp::CompletionItem {
18761            label: format!("item_{}", i),
18762            insert_text: Some(format!("item_{}", i)),
18763            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18764            ..lsp::CompletionItem::default()
18765        }))
18766        .collect::<Vec<_>>();
18767
18768    let default_commit_characters = vec!["?".to_string()];
18769    let default_data = json!({ "default": "data"});
18770    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18771    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18772    let default_edit_range = lsp::Range {
18773        start: lsp::Position {
18774            line: 0,
18775            character: 5,
18776        },
18777        end: lsp::Position {
18778            line: 0,
18779            character: 5,
18780        },
18781    };
18782
18783    let mut cx = EditorLspTestContext::new_rust(
18784        lsp::ServerCapabilities {
18785            completion_provider: Some(lsp::CompletionOptions {
18786                trigger_characters: Some(vec![".".to_string()]),
18787                resolve_provider: Some(true),
18788                ..Default::default()
18789            }),
18790            ..Default::default()
18791        },
18792        cx,
18793    )
18794    .await;
18795
18796    cx.set_state("fn main() { let a = 2ˇ; }");
18797    cx.simulate_keystroke(".");
18798
18799    let completion_data = default_data.clone();
18800    let completion_characters = default_commit_characters.clone();
18801    let completion_items = items.clone();
18802    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18803        let default_data = completion_data.clone();
18804        let default_commit_characters = completion_characters.clone();
18805        let items = completion_items.clone();
18806        async move {
18807            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18808                items,
18809                item_defaults: Some(lsp::CompletionListItemDefaults {
18810                    data: Some(default_data.clone()),
18811                    commit_characters: Some(default_commit_characters.clone()),
18812                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18813                        default_edit_range,
18814                    )),
18815                    insert_text_format: Some(default_insert_text_format),
18816                    insert_text_mode: Some(default_insert_text_mode),
18817                }),
18818                ..lsp::CompletionList::default()
18819            })))
18820        }
18821    })
18822    .next()
18823    .await;
18824
18825    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18826    cx.lsp
18827        .server
18828        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18829            let closure_resolved_items = resolved_items.clone();
18830            move |item_to_resolve, _| {
18831                let closure_resolved_items = closure_resolved_items.clone();
18832                async move {
18833                    closure_resolved_items.lock().push(item_to_resolve.clone());
18834                    Ok(item_to_resolve)
18835                }
18836            }
18837        })
18838        .detach();
18839
18840    cx.condition(|editor, _| editor.context_menu_visible())
18841        .await;
18842    cx.run_until_parked();
18843    cx.update_editor(|editor, _, _| {
18844        let menu = editor.context_menu.borrow_mut();
18845        match menu.as_ref().expect("should have the completions menu") {
18846            CodeContextMenu::Completions(completions_menu) => {
18847                assert_eq!(
18848                    completions_menu
18849                        .entries
18850                        .borrow()
18851                        .iter()
18852                        .map(|mat| mat.string.clone())
18853                        .collect::<Vec<String>>(),
18854                    items
18855                        .iter()
18856                        .map(|completion| completion.label.clone())
18857                        .collect::<Vec<String>>()
18858                );
18859            }
18860            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18861        }
18862    });
18863    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18864    // with 4 from the end.
18865    assert_eq!(
18866        *resolved_items.lock(),
18867        [&items[0..16], &items[items.len() - 4..items.len()]]
18868            .concat()
18869            .iter()
18870            .cloned()
18871            .map(|mut item| {
18872                if item.data.is_none() {
18873                    item.data = Some(default_data.clone());
18874                }
18875                item
18876            })
18877            .collect::<Vec<lsp::CompletionItem>>(),
18878        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18879    );
18880    resolved_items.lock().clear();
18881
18882    cx.update_editor(|editor, window, cx| {
18883        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18884    });
18885    cx.run_until_parked();
18886    // Completions that have already been resolved are skipped.
18887    assert_eq!(
18888        *resolved_items.lock(),
18889        items[items.len() - 17..items.len() - 4]
18890            .iter()
18891            .cloned()
18892            .map(|mut item| {
18893                if item.data.is_none() {
18894                    item.data = Some(default_data.clone());
18895                }
18896                item
18897            })
18898            .collect::<Vec<lsp::CompletionItem>>()
18899    );
18900    resolved_items.lock().clear();
18901}
18902
18903#[gpui::test]
18904async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18905    init_test(cx, |_| {});
18906
18907    let mut cx = EditorLspTestContext::new(
18908        Language::new(
18909            LanguageConfig {
18910                matcher: LanguageMatcher {
18911                    path_suffixes: vec!["jsx".into()],
18912                    ..Default::default()
18913                },
18914                overrides: [(
18915                    "element".into(),
18916                    LanguageConfigOverride {
18917                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18918                        ..Default::default()
18919                    },
18920                )]
18921                .into_iter()
18922                .collect(),
18923                ..Default::default()
18924            },
18925            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18926        )
18927        .with_override_query("(jsx_self_closing_element) @element")
18928        .unwrap(),
18929        lsp::ServerCapabilities {
18930            completion_provider: Some(lsp::CompletionOptions {
18931                trigger_characters: Some(vec![":".to_string()]),
18932                ..Default::default()
18933            }),
18934            ..Default::default()
18935        },
18936        cx,
18937    )
18938    .await;
18939
18940    cx.lsp
18941        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18942            Ok(Some(lsp::CompletionResponse::Array(vec![
18943                lsp::CompletionItem {
18944                    label: "bg-blue".into(),
18945                    ..Default::default()
18946                },
18947                lsp::CompletionItem {
18948                    label: "bg-red".into(),
18949                    ..Default::default()
18950                },
18951                lsp::CompletionItem {
18952                    label: "bg-yellow".into(),
18953                    ..Default::default()
18954                },
18955            ])))
18956        });
18957
18958    cx.set_state(r#"<p class="bgˇ" />"#);
18959
18960    // Trigger completion when typing a dash, because the dash is an extra
18961    // word character in the 'element' scope, which contains the cursor.
18962    cx.simulate_keystroke("-");
18963    cx.executor().run_until_parked();
18964    cx.update_editor(|editor, _, _| {
18965        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18966        {
18967            assert_eq!(
18968                completion_menu_entries(menu),
18969                &["bg-blue", "bg-red", "bg-yellow"]
18970            );
18971        } else {
18972            panic!("expected completion menu to be open");
18973        }
18974    });
18975
18976    cx.simulate_keystroke("l");
18977    cx.executor().run_until_parked();
18978    cx.update_editor(|editor, _, _| {
18979        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18980        {
18981            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18982        } else {
18983            panic!("expected completion menu to be open");
18984        }
18985    });
18986
18987    // When filtering completions, consider the character after the '-' to
18988    // be the start of a subword.
18989    cx.set_state(r#"<p class="yelˇ" />"#);
18990    cx.simulate_keystroke("l");
18991    cx.executor().run_until_parked();
18992    cx.update_editor(|editor, _, _| {
18993        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18994        {
18995            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18996        } else {
18997            panic!("expected completion menu to be open");
18998        }
18999    });
19000}
19001
19002fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19003    let entries = menu.entries.borrow();
19004    entries.iter().map(|mat| mat.string.clone()).collect()
19005}
19006
19007#[gpui::test]
19008async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19009    init_test(cx, |settings| {
19010        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19011    });
19012
19013    let fs = FakeFs::new(cx.executor());
19014    fs.insert_file(path!("/file.ts"), Default::default()).await;
19015
19016    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19017    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19018
19019    language_registry.add(Arc::new(Language::new(
19020        LanguageConfig {
19021            name: "TypeScript".into(),
19022            matcher: LanguageMatcher {
19023                path_suffixes: vec!["ts".to_string()],
19024                ..Default::default()
19025            },
19026            ..Default::default()
19027        },
19028        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19029    )));
19030    update_test_language_settings(cx, |settings| {
19031        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19032    });
19033
19034    let test_plugin = "test_plugin";
19035    let _ = language_registry.register_fake_lsp(
19036        "TypeScript",
19037        FakeLspAdapter {
19038            prettier_plugins: vec![test_plugin],
19039            ..Default::default()
19040        },
19041    );
19042
19043    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19044    let buffer = project
19045        .update(cx, |project, cx| {
19046            project.open_local_buffer(path!("/file.ts"), cx)
19047        })
19048        .await
19049        .unwrap();
19050
19051    let buffer_text = "one\ntwo\nthree\n";
19052    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19053    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19054    editor.update_in(cx, |editor, window, cx| {
19055        editor.set_text(buffer_text, window, cx)
19056    });
19057
19058    editor
19059        .update_in(cx, |editor, window, cx| {
19060            editor.perform_format(
19061                project.clone(),
19062                FormatTrigger::Manual,
19063                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19064                window,
19065                cx,
19066            )
19067        })
19068        .unwrap()
19069        .await;
19070    assert_eq!(
19071        editor.update(cx, |editor, cx| editor.text(cx)),
19072        buffer_text.to_string() + prettier_format_suffix,
19073        "Test prettier formatting was not applied to the original buffer text",
19074    );
19075
19076    update_test_language_settings(cx, |settings| {
19077        settings.defaults.formatter = Some(FormatterList::default())
19078    });
19079    let format = editor.update_in(cx, |editor, window, cx| {
19080        editor.perform_format(
19081            project.clone(),
19082            FormatTrigger::Manual,
19083            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19084            window,
19085            cx,
19086        )
19087    });
19088    format.await.unwrap();
19089    assert_eq!(
19090        editor.update(cx, |editor, cx| editor.text(cx)),
19091        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19092        "Autoformatting (via test prettier) was not applied to the original buffer text",
19093    );
19094}
19095
19096#[gpui::test]
19097async fn test_addition_reverts(cx: &mut TestAppContext) {
19098    init_test(cx, |_| {});
19099    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19100    let base_text = indoc! {r#"
19101        struct Row;
19102        struct Row1;
19103        struct Row2;
19104
19105        struct Row4;
19106        struct Row5;
19107        struct Row6;
19108
19109        struct Row8;
19110        struct Row9;
19111        struct Row10;"#};
19112
19113    // When addition hunks are not adjacent to carets, no hunk revert is performed
19114    assert_hunk_revert(
19115        indoc! {r#"struct Row;
19116                   struct Row1;
19117                   struct Row1.1;
19118                   struct Row1.2;
19119                   struct Row2;ˇ
19120
19121                   struct Row4;
19122                   struct Row5;
19123                   struct Row6;
19124
19125                   struct Row8;
19126                   ˇstruct Row9;
19127                   struct Row9.1;
19128                   struct Row9.2;
19129                   struct Row9.3;
19130                   struct Row10;"#},
19131        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19132        indoc! {r#"struct Row;
19133                   struct Row1;
19134                   struct Row1.1;
19135                   struct Row1.2;
19136                   struct Row2;ˇ
19137
19138                   struct Row4;
19139                   struct Row5;
19140                   struct Row6;
19141
19142                   struct Row8;
19143                   ˇstruct Row9;
19144                   struct Row9.1;
19145                   struct Row9.2;
19146                   struct Row9.3;
19147                   struct Row10;"#},
19148        base_text,
19149        &mut cx,
19150    );
19151    // Same for selections
19152    assert_hunk_revert(
19153        indoc! {r#"struct Row;
19154                   struct Row1;
19155                   struct Row2;
19156                   struct Row2.1;
19157                   struct Row2.2;
19158                   «ˇ
19159                   struct Row4;
19160                   struct» Row5;
19161                   «struct Row6;
19162                   ˇ»
19163                   struct Row9.1;
19164                   struct Row9.2;
19165                   struct Row9.3;
19166                   struct Row8;
19167                   struct Row9;
19168                   struct Row10;"#},
19169        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19170        indoc! {r#"struct Row;
19171                   struct Row1;
19172                   struct Row2;
19173                   struct Row2.1;
19174                   struct Row2.2;
19175                   «ˇ
19176                   struct Row4;
19177                   struct» Row5;
19178                   «struct Row6;
19179                   ˇ»
19180                   struct Row9.1;
19181                   struct Row9.2;
19182                   struct Row9.3;
19183                   struct Row8;
19184                   struct Row9;
19185                   struct Row10;"#},
19186        base_text,
19187        &mut cx,
19188    );
19189
19190    // When carets and selections intersect the addition hunks, those are reverted.
19191    // Adjacent carets got merged.
19192    assert_hunk_revert(
19193        indoc! {r#"struct Row;
19194                   ˇ// something on the top
19195                   struct Row1;
19196                   struct Row2;
19197                   struct Roˇw3.1;
19198                   struct Row2.2;
19199                   struct Row2.3;ˇ
19200
19201                   struct Row4;
19202                   struct ˇRow5.1;
19203                   struct Row5.2;
19204                   struct «Rowˇ»5.3;
19205                   struct Row5;
19206                   struct Row6;
19207                   ˇ
19208                   struct Row9.1;
19209                   struct «Rowˇ»9.2;
19210                   struct «ˇRow»9.3;
19211                   struct Row8;
19212                   struct Row9;
19213                   «ˇ// something on bottom»
19214                   struct Row10;"#},
19215        vec![
19216            DiffHunkStatusKind::Added,
19217            DiffHunkStatusKind::Added,
19218            DiffHunkStatusKind::Added,
19219            DiffHunkStatusKind::Added,
19220            DiffHunkStatusKind::Added,
19221        ],
19222        indoc! {r#"struct Row;
19223                   ˇstruct Row1;
19224                   struct Row2;
19225                   ˇ
19226                   struct Row4;
19227                   ˇstruct Row5;
19228                   struct Row6;
19229                   ˇ
19230                   ˇstruct Row8;
19231                   struct Row9;
19232                   ˇstruct Row10;"#},
19233        base_text,
19234        &mut cx,
19235    );
19236}
19237
19238#[gpui::test]
19239async fn test_modification_reverts(cx: &mut TestAppContext) {
19240    init_test(cx, |_| {});
19241    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19242    let base_text = indoc! {r#"
19243        struct Row;
19244        struct Row1;
19245        struct Row2;
19246
19247        struct Row4;
19248        struct Row5;
19249        struct Row6;
19250
19251        struct Row8;
19252        struct Row9;
19253        struct Row10;"#};
19254
19255    // Modification hunks behave the same as the addition ones.
19256    assert_hunk_revert(
19257        indoc! {r#"struct Row;
19258                   struct Row1;
19259                   struct Row33;
19260                   ˇ
19261                   struct Row4;
19262                   struct Row5;
19263                   struct Row6;
19264                   ˇ
19265                   struct Row99;
19266                   struct Row9;
19267                   struct Row10;"#},
19268        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19269        indoc! {r#"struct Row;
19270                   struct Row1;
19271                   struct Row33;
19272                   ˇ
19273                   struct Row4;
19274                   struct Row5;
19275                   struct Row6;
19276                   ˇ
19277                   struct Row99;
19278                   struct Row9;
19279                   struct Row10;"#},
19280        base_text,
19281        &mut cx,
19282    );
19283    assert_hunk_revert(
19284        indoc! {r#"struct Row;
19285                   struct Row1;
19286                   struct Row33;
19287                   «ˇ
19288                   struct Row4;
19289                   struct» Row5;
19290                   «struct Row6;
19291                   ˇ»
19292                   struct Row99;
19293                   struct Row9;
19294                   struct Row10;"#},
19295        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19296        indoc! {r#"struct Row;
19297                   struct Row1;
19298                   struct Row33;
19299                   «ˇ
19300                   struct Row4;
19301                   struct» Row5;
19302                   «struct Row6;
19303                   ˇ»
19304                   struct Row99;
19305                   struct Row9;
19306                   struct Row10;"#},
19307        base_text,
19308        &mut cx,
19309    );
19310
19311    assert_hunk_revert(
19312        indoc! {r#"ˇstruct Row1.1;
19313                   struct Row1;
19314                   «ˇstr»uct Row22;
19315
19316                   struct ˇRow44;
19317                   struct Row5;
19318                   struct «Rˇ»ow66;ˇ
19319
19320                   «struˇ»ct Row88;
19321                   struct Row9;
19322                   struct Row1011;ˇ"#},
19323        vec![
19324            DiffHunkStatusKind::Modified,
19325            DiffHunkStatusKind::Modified,
19326            DiffHunkStatusKind::Modified,
19327            DiffHunkStatusKind::Modified,
19328            DiffHunkStatusKind::Modified,
19329            DiffHunkStatusKind::Modified,
19330        ],
19331        indoc! {r#"struct Row;
19332                   ˇstruct Row1;
19333                   struct Row2;
19334                   ˇ
19335                   struct Row4;
19336                   ˇstruct Row5;
19337                   struct Row6;
19338                   ˇ
19339                   struct Row8;
19340                   ˇstruct Row9;
19341                   struct Row10;ˇ"#},
19342        base_text,
19343        &mut cx,
19344    );
19345}
19346
19347#[gpui::test]
19348async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19349    init_test(cx, |_| {});
19350    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19351    let base_text = indoc! {r#"
19352        one
19353
19354        two
19355        three
19356        "#};
19357
19358    cx.set_head_text(base_text);
19359    cx.set_state("\nˇ\n");
19360    cx.executor().run_until_parked();
19361    cx.update_editor(|editor, _window, cx| {
19362        editor.expand_selected_diff_hunks(cx);
19363    });
19364    cx.executor().run_until_parked();
19365    cx.update_editor(|editor, window, cx| {
19366        editor.backspace(&Default::default(), window, cx);
19367    });
19368    cx.run_until_parked();
19369    cx.assert_state_with_diff(
19370        indoc! {r#"
19371
19372        - two
19373        - threeˇ
19374        +
19375        "#}
19376        .to_string(),
19377    );
19378}
19379
19380#[gpui::test]
19381async fn test_deletion_reverts(cx: &mut TestAppContext) {
19382    init_test(cx, |_| {});
19383    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19384    let base_text = indoc! {r#"struct Row;
19385struct Row1;
19386struct Row2;
19387
19388struct Row4;
19389struct Row5;
19390struct Row6;
19391
19392struct Row8;
19393struct Row9;
19394struct Row10;"#};
19395
19396    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19397    assert_hunk_revert(
19398        indoc! {r#"struct Row;
19399                   struct Row2;
19400
19401                   ˇstruct Row4;
19402                   struct Row5;
19403                   struct Row6;
19404                   ˇ
19405                   struct Row8;
19406                   struct Row10;"#},
19407        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19408        indoc! {r#"struct Row;
19409                   struct Row2;
19410
19411                   ˇstruct Row4;
19412                   struct Row5;
19413                   struct Row6;
19414                   ˇ
19415                   struct Row8;
19416                   struct Row10;"#},
19417        base_text,
19418        &mut cx,
19419    );
19420    assert_hunk_revert(
19421        indoc! {r#"struct Row;
19422                   struct Row2;
19423
19424                   «ˇstruct Row4;
19425                   struct» Row5;
19426                   «struct Row6;
19427                   ˇ»
19428                   struct Row8;
19429                   struct Row10;"#},
19430        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19431        indoc! {r#"struct Row;
19432                   struct Row2;
19433
19434                   «ˇstruct Row4;
19435                   struct» Row5;
19436                   «struct Row6;
19437                   ˇ»
19438                   struct Row8;
19439                   struct Row10;"#},
19440        base_text,
19441        &mut cx,
19442    );
19443
19444    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19445    assert_hunk_revert(
19446        indoc! {r#"struct Row;
19447                   ˇstruct Row2;
19448
19449                   struct Row4;
19450                   struct Row5;
19451                   struct Row6;
19452
19453                   struct Row8;ˇ
19454                   struct Row10;"#},
19455        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19456        indoc! {r#"struct Row;
19457                   struct Row1;
19458                   ˇstruct Row2;
19459
19460                   struct Row4;
19461                   struct Row5;
19462                   struct Row6;
19463
19464                   struct Row8;ˇ
19465                   struct Row9;
19466                   struct Row10;"#},
19467        base_text,
19468        &mut cx,
19469    );
19470    assert_hunk_revert(
19471        indoc! {r#"struct Row;
19472                   struct Row2«ˇ;
19473                   struct Row4;
19474                   struct» Row5;
19475                   «struct Row6;
19476
19477                   struct Row8;ˇ»
19478                   struct Row10;"#},
19479        vec![
19480            DiffHunkStatusKind::Deleted,
19481            DiffHunkStatusKind::Deleted,
19482            DiffHunkStatusKind::Deleted,
19483        ],
19484        indoc! {r#"struct Row;
19485                   struct Row1;
19486                   struct Row2«ˇ;
19487
19488                   struct Row4;
19489                   struct» Row5;
19490                   «struct Row6;
19491
19492                   struct Row8;ˇ»
19493                   struct Row9;
19494                   struct Row10;"#},
19495        base_text,
19496        &mut cx,
19497    );
19498}
19499
19500#[gpui::test]
19501async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19502    init_test(cx, |_| {});
19503
19504    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19505    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19506    let base_text_3 =
19507        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19508
19509    let text_1 = edit_first_char_of_every_line(base_text_1);
19510    let text_2 = edit_first_char_of_every_line(base_text_2);
19511    let text_3 = edit_first_char_of_every_line(base_text_3);
19512
19513    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19514    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19515    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19516
19517    let multibuffer = cx.new(|cx| {
19518        let mut multibuffer = MultiBuffer::new(ReadWrite);
19519        multibuffer.push_excerpts(
19520            buffer_1.clone(),
19521            [
19522                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19523                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19524                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19525            ],
19526            cx,
19527        );
19528        multibuffer.push_excerpts(
19529            buffer_2.clone(),
19530            [
19531                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19532                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19533                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19534            ],
19535            cx,
19536        );
19537        multibuffer.push_excerpts(
19538            buffer_3.clone(),
19539            [
19540                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19541                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19542                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19543            ],
19544            cx,
19545        );
19546        multibuffer
19547    });
19548
19549    let fs = FakeFs::new(cx.executor());
19550    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19551    let (editor, cx) = cx
19552        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19553    editor.update_in(cx, |editor, _window, cx| {
19554        for (buffer, diff_base) in [
19555            (buffer_1.clone(), base_text_1),
19556            (buffer_2.clone(), base_text_2),
19557            (buffer_3.clone(), base_text_3),
19558        ] {
19559            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19560            editor
19561                .buffer
19562                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19563        }
19564    });
19565    cx.executor().run_until_parked();
19566
19567    editor.update_in(cx, |editor, window, cx| {
19568        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}");
19569        editor.select_all(&SelectAll, window, cx);
19570        editor.git_restore(&Default::default(), window, cx);
19571    });
19572    cx.executor().run_until_parked();
19573
19574    // When all ranges are selected, all buffer hunks are reverted.
19575    editor.update(cx, |editor, cx| {
19576        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");
19577    });
19578    buffer_1.update(cx, |buffer, _| {
19579        assert_eq!(buffer.text(), base_text_1);
19580    });
19581    buffer_2.update(cx, |buffer, _| {
19582        assert_eq!(buffer.text(), base_text_2);
19583    });
19584    buffer_3.update(cx, |buffer, _| {
19585        assert_eq!(buffer.text(), base_text_3);
19586    });
19587
19588    editor.update_in(cx, |editor, window, cx| {
19589        editor.undo(&Default::default(), window, cx);
19590    });
19591
19592    editor.update_in(cx, |editor, window, cx| {
19593        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19594            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19595        });
19596        editor.git_restore(&Default::default(), window, cx);
19597    });
19598
19599    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19600    // but not affect buffer_2 and its related excerpts.
19601    editor.update(cx, |editor, cx| {
19602        assert_eq!(
19603            editor.text(cx),
19604            "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}"
19605        );
19606    });
19607    buffer_1.update(cx, |buffer, _| {
19608        assert_eq!(buffer.text(), base_text_1);
19609    });
19610    buffer_2.update(cx, |buffer, _| {
19611        assert_eq!(
19612            buffer.text(),
19613            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19614        );
19615    });
19616    buffer_3.update(cx, |buffer, _| {
19617        assert_eq!(
19618            buffer.text(),
19619            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19620        );
19621    });
19622
19623    fn edit_first_char_of_every_line(text: &str) -> String {
19624        text.split('\n')
19625            .map(|line| format!("X{}", &line[1..]))
19626            .collect::<Vec<_>>()
19627            .join("\n")
19628    }
19629}
19630
19631#[gpui::test]
19632async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19633    init_test(cx, |_| {});
19634
19635    let cols = 4;
19636    let rows = 10;
19637    let sample_text_1 = sample_text(rows, cols, 'a');
19638    assert_eq!(
19639        sample_text_1,
19640        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19641    );
19642    let sample_text_2 = sample_text(rows, cols, 'l');
19643    assert_eq!(
19644        sample_text_2,
19645        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19646    );
19647    let sample_text_3 = sample_text(rows, cols, 'v');
19648    assert_eq!(
19649        sample_text_3,
19650        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19651    );
19652
19653    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19654    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19655    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19656
19657    let multi_buffer = cx.new(|cx| {
19658        let mut multibuffer = MultiBuffer::new(ReadWrite);
19659        multibuffer.push_excerpts(
19660            buffer_1.clone(),
19661            [
19662                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19663                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19664                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19665            ],
19666            cx,
19667        );
19668        multibuffer.push_excerpts(
19669            buffer_2.clone(),
19670            [
19671                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19672                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19673                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19674            ],
19675            cx,
19676        );
19677        multibuffer.push_excerpts(
19678            buffer_3.clone(),
19679            [
19680                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19681                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19682                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19683            ],
19684            cx,
19685        );
19686        multibuffer
19687    });
19688
19689    let fs = FakeFs::new(cx.executor());
19690    fs.insert_tree(
19691        "/a",
19692        json!({
19693            "main.rs": sample_text_1,
19694            "other.rs": sample_text_2,
19695            "lib.rs": sample_text_3,
19696        }),
19697    )
19698    .await;
19699    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19700    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19701    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19702    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19703        Editor::new(
19704            EditorMode::full(),
19705            multi_buffer,
19706            Some(project.clone()),
19707            window,
19708            cx,
19709        )
19710    });
19711    let multibuffer_item_id = workspace
19712        .update(cx, |workspace, window, cx| {
19713            assert!(
19714                workspace.active_item(cx).is_none(),
19715                "active item should be None before the first item is added"
19716            );
19717            workspace.add_item_to_active_pane(
19718                Box::new(multi_buffer_editor.clone()),
19719                None,
19720                true,
19721                window,
19722                cx,
19723            );
19724            let active_item = workspace
19725                .active_item(cx)
19726                .expect("should have an active item after adding the multi buffer");
19727            assert_eq!(
19728                active_item.buffer_kind(cx),
19729                ItemBufferKind::Multibuffer,
19730                "A multi buffer was expected to active after adding"
19731            );
19732            active_item.item_id()
19733        })
19734        .unwrap();
19735    cx.executor().run_until_parked();
19736
19737    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19738        editor.change_selections(
19739            SelectionEffects::scroll(Autoscroll::Next),
19740            window,
19741            cx,
19742            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
19743        );
19744        editor.open_excerpts(&OpenExcerpts, window, cx);
19745    });
19746    cx.executor().run_until_parked();
19747    let first_item_id = workspace
19748        .update(cx, |workspace, window, cx| {
19749            let active_item = workspace
19750                .active_item(cx)
19751                .expect("should have an active item after navigating into the 1st buffer");
19752            let first_item_id = active_item.item_id();
19753            assert_ne!(
19754                first_item_id, multibuffer_item_id,
19755                "Should navigate into the 1st buffer and activate it"
19756            );
19757            assert_eq!(
19758                active_item.buffer_kind(cx),
19759                ItemBufferKind::Singleton,
19760                "New active item should be a singleton buffer"
19761            );
19762            assert_eq!(
19763                active_item
19764                    .act_as::<Editor>(cx)
19765                    .expect("should have navigated into an editor for the 1st buffer")
19766                    .read(cx)
19767                    .text(cx),
19768                sample_text_1
19769            );
19770
19771            workspace
19772                .go_back(workspace.active_pane().downgrade(), window, cx)
19773                .detach_and_log_err(cx);
19774
19775            first_item_id
19776        })
19777        .unwrap();
19778    cx.executor().run_until_parked();
19779    workspace
19780        .update(cx, |workspace, _, cx| {
19781            let active_item = workspace
19782                .active_item(cx)
19783                .expect("should have an active item after navigating back");
19784            assert_eq!(
19785                active_item.item_id(),
19786                multibuffer_item_id,
19787                "Should navigate back to the multi buffer"
19788            );
19789            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19790        })
19791        .unwrap();
19792
19793    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19794        editor.change_selections(
19795            SelectionEffects::scroll(Autoscroll::Next),
19796            window,
19797            cx,
19798            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
19799        );
19800        editor.open_excerpts(&OpenExcerpts, window, cx);
19801    });
19802    cx.executor().run_until_parked();
19803    let second_item_id = workspace
19804        .update(cx, |workspace, window, cx| {
19805            let active_item = workspace
19806                .active_item(cx)
19807                .expect("should have an active item after navigating into the 2nd buffer");
19808            let second_item_id = active_item.item_id();
19809            assert_ne!(
19810                second_item_id, multibuffer_item_id,
19811                "Should navigate away from the multibuffer"
19812            );
19813            assert_ne!(
19814                second_item_id, first_item_id,
19815                "Should navigate into the 2nd buffer and activate it"
19816            );
19817            assert_eq!(
19818                active_item.buffer_kind(cx),
19819                ItemBufferKind::Singleton,
19820                "New active item should be a singleton buffer"
19821            );
19822            assert_eq!(
19823                active_item
19824                    .act_as::<Editor>(cx)
19825                    .expect("should have navigated into an editor")
19826                    .read(cx)
19827                    .text(cx),
19828                sample_text_2
19829            );
19830
19831            workspace
19832                .go_back(workspace.active_pane().downgrade(), window, cx)
19833                .detach_and_log_err(cx);
19834
19835            second_item_id
19836        })
19837        .unwrap();
19838    cx.executor().run_until_parked();
19839    workspace
19840        .update(cx, |workspace, _, cx| {
19841            let active_item = workspace
19842                .active_item(cx)
19843                .expect("should have an active item after navigating back from the 2nd buffer");
19844            assert_eq!(
19845                active_item.item_id(),
19846                multibuffer_item_id,
19847                "Should navigate back from the 2nd buffer to the multi buffer"
19848            );
19849            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19850        })
19851        .unwrap();
19852
19853    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19854        editor.change_selections(
19855            SelectionEffects::scroll(Autoscroll::Next),
19856            window,
19857            cx,
19858            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
19859        );
19860        editor.open_excerpts(&OpenExcerpts, window, cx);
19861    });
19862    cx.executor().run_until_parked();
19863    workspace
19864        .update(cx, |workspace, window, cx| {
19865            let active_item = workspace
19866                .active_item(cx)
19867                .expect("should have an active item after navigating into the 3rd buffer");
19868            let third_item_id = active_item.item_id();
19869            assert_ne!(
19870                third_item_id, multibuffer_item_id,
19871                "Should navigate into the 3rd buffer and activate it"
19872            );
19873            assert_ne!(third_item_id, first_item_id);
19874            assert_ne!(third_item_id, second_item_id);
19875            assert_eq!(
19876                active_item.buffer_kind(cx),
19877                ItemBufferKind::Singleton,
19878                "New active item should be a singleton buffer"
19879            );
19880            assert_eq!(
19881                active_item
19882                    .act_as::<Editor>(cx)
19883                    .expect("should have navigated into an editor")
19884                    .read(cx)
19885                    .text(cx),
19886                sample_text_3
19887            );
19888
19889            workspace
19890                .go_back(workspace.active_pane().downgrade(), window, cx)
19891                .detach_and_log_err(cx);
19892        })
19893        .unwrap();
19894    cx.executor().run_until_parked();
19895    workspace
19896        .update(cx, |workspace, _, cx| {
19897            let active_item = workspace
19898                .active_item(cx)
19899                .expect("should have an active item after navigating back from the 3rd buffer");
19900            assert_eq!(
19901                active_item.item_id(),
19902                multibuffer_item_id,
19903                "Should navigate back from the 3rd buffer to the multi buffer"
19904            );
19905            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19906        })
19907        .unwrap();
19908}
19909
19910#[gpui::test]
19911async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19912    init_test(cx, |_| {});
19913
19914    let mut cx = EditorTestContext::new(cx).await;
19915
19916    let diff_base = r#"
19917        use some::mod;
19918
19919        const A: u32 = 42;
19920
19921        fn main() {
19922            println!("hello");
19923
19924            println!("world");
19925        }
19926        "#
19927    .unindent();
19928
19929    cx.set_state(
19930        &r#"
19931        use some::modified;
19932
19933        ˇ
19934        fn main() {
19935            println!("hello there");
19936
19937            println!("around the");
19938            println!("world");
19939        }
19940        "#
19941        .unindent(),
19942    );
19943
19944    cx.set_head_text(&diff_base);
19945    executor.run_until_parked();
19946
19947    cx.update_editor(|editor, window, cx| {
19948        editor.go_to_next_hunk(&GoToHunk, window, cx);
19949        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19950    });
19951    executor.run_until_parked();
19952    cx.assert_state_with_diff(
19953        r#"
19954          use some::modified;
19955
19956
19957          fn main() {
19958        -     println!("hello");
19959        + ˇ    println!("hello there");
19960
19961              println!("around the");
19962              println!("world");
19963          }
19964        "#
19965        .unindent(),
19966    );
19967
19968    cx.update_editor(|editor, window, cx| {
19969        for _ in 0..2 {
19970            editor.go_to_next_hunk(&GoToHunk, window, cx);
19971            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19972        }
19973    });
19974    executor.run_until_parked();
19975    cx.assert_state_with_diff(
19976        r#"
19977        - use some::mod;
19978        + ˇuse some::modified;
19979
19980
19981          fn main() {
19982        -     println!("hello");
19983        +     println!("hello there");
19984
19985        +     println!("around the");
19986              println!("world");
19987          }
19988        "#
19989        .unindent(),
19990    );
19991
19992    cx.update_editor(|editor, window, cx| {
19993        editor.go_to_next_hunk(&GoToHunk, window, cx);
19994        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19995    });
19996    executor.run_until_parked();
19997    cx.assert_state_with_diff(
19998        r#"
19999        - use some::mod;
20000        + use some::modified;
20001
20002        - const A: u32 = 42;
20003          ˇ
20004          fn main() {
20005        -     println!("hello");
20006        +     println!("hello there");
20007
20008        +     println!("around the");
20009              println!("world");
20010          }
20011        "#
20012        .unindent(),
20013    );
20014
20015    cx.update_editor(|editor, window, cx| {
20016        editor.cancel(&Cancel, window, cx);
20017    });
20018
20019    cx.assert_state_with_diff(
20020        r#"
20021          use some::modified;
20022
20023          ˇ
20024          fn main() {
20025              println!("hello there");
20026
20027              println!("around the");
20028              println!("world");
20029          }
20030        "#
20031        .unindent(),
20032    );
20033}
20034
20035#[gpui::test]
20036async fn test_diff_base_change_with_expanded_diff_hunks(
20037    executor: BackgroundExecutor,
20038    cx: &mut TestAppContext,
20039) {
20040    init_test(cx, |_| {});
20041
20042    let mut cx = EditorTestContext::new(cx).await;
20043
20044    let diff_base = r#"
20045        use some::mod1;
20046        use some::mod2;
20047
20048        const A: u32 = 42;
20049        const B: u32 = 42;
20050        const C: u32 = 42;
20051
20052        fn main() {
20053            println!("hello");
20054
20055            println!("world");
20056        }
20057        "#
20058    .unindent();
20059
20060    cx.set_state(
20061        &r#"
20062        use some::mod2;
20063
20064        const A: u32 = 42;
20065        const C: u32 = 42;
20066
20067        fn main(ˇ) {
20068            //println!("hello");
20069
20070            println!("world");
20071            //
20072            //
20073        }
20074        "#
20075        .unindent(),
20076    );
20077
20078    cx.set_head_text(&diff_base);
20079    executor.run_until_parked();
20080
20081    cx.update_editor(|editor, window, cx| {
20082        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20083    });
20084    executor.run_until_parked();
20085    cx.assert_state_with_diff(
20086        r#"
20087        - use some::mod1;
20088          use some::mod2;
20089
20090          const A: u32 = 42;
20091        - const B: u32 = 42;
20092          const C: u32 = 42;
20093
20094          fn main(ˇ) {
20095        -     println!("hello");
20096        +     //println!("hello");
20097
20098              println!("world");
20099        +     //
20100        +     //
20101          }
20102        "#
20103        .unindent(),
20104    );
20105
20106    cx.set_head_text("new diff base!");
20107    executor.run_until_parked();
20108    cx.assert_state_with_diff(
20109        r#"
20110        - new diff base!
20111        + use some::mod2;
20112        +
20113        + const A: u32 = 42;
20114        + const C: u32 = 42;
20115        +
20116        + fn main(ˇ) {
20117        +     //println!("hello");
20118        +
20119        +     println!("world");
20120        +     //
20121        +     //
20122        + }
20123        "#
20124        .unindent(),
20125    );
20126}
20127
20128#[gpui::test]
20129async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20130    init_test(cx, |_| {});
20131
20132    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20133    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20134    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20135    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20136    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20137    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20138
20139    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20140    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20141    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20142
20143    let multi_buffer = cx.new(|cx| {
20144        let mut multibuffer = MultiBuffer::new(ReadWrite);
20145        multibuffer.push_excerpts(
20146            buffer_1.clone(),
20147            [
20148                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20149                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20150                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20151            ],
20152            cx,
20153        );
20154        multibuffer.push_excerpts(
20155            buffer_2.clone(),
20156            [
20157                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20158                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20159                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20160            ],
20161            cx,
20162        );
20163        multibuffer.push_excerpts(
20164            buffer_3.clone(),
20165            [
20166                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20167                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20168                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20169            ],
20170            cx,
20171        );
20172        multibuffer
20173    });
20174
20175    let editor =
20176        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20177    editor
20178        .update(cx, |editor, _window, cx| {
20179            for (buffer, diff_base) in [
20180                (buffer_1.clone(), file_1_old),
20181                (buffer_2.clone(), file_2_old),
20182                (buffer_3.clone(), file_3_old),
20183            ] {
20184                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
20185                editor
20186                    .buffer
20187                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20188            }
20189        })
20190        .unwrap();
20191
20192    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20193    cx.run_until_parked();
20194
20195    cx.assert_editor_state(
20196        &"
20197            ˇaaa
20198            ccc
20199            ddd
20200
20201            ggg
20202            hhh
20203
20204
20205            lll
20206            mmm
20207            NNN
20208
20209            qqq
20210            rrr
20211
20212            uuu
20213            111
20214            222
20215            333
20216
20217            666
20218            777
20219
20220            000
20221            !!!"
20222        .unindent(),
20223    );
20224
20225    cx.update_editor(|editor, window, cx| {
20226        editor.select_all(&SelectAll, window, cx);
20227        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20228    });
20229    cx.executor().run_until_parked();
20230
20231    cx.assert_state_with_diff(
20232        "
20233            «aaa
20234          - bbb
20235            ccc
20236            ddd
20237
20238            ggg
20239            hhh
20240
20241
20242            lll
20243            mmm
20244          - nnn
20245          + NNN
20246
20247            qqq
20248            rrr
20249
20250            uuu
20251            111
20252            222
20253            333
20254
20255          + 666
20256            777
20257
20258            000
20259            !!!ˇ»"
20260            .unindent(),
20261    );
20262}
20263
20264#[gpui::test]
20265async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20266    init_test(cx, |_| {});
20267
20268    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20269    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20270
20271    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20272    let multi_buffer = cx.new(|cx| {
20273        let mut multibuffer = MultiBuffer::new(ReadWrite);
20274        multibuffer.push_excerpts(
20275            buffer.clone(),
20276            [
20277                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20278                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20279                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20280            ],
20281            cx,
20282        );
20283        multibuffer
20284    });
20285
20286    let editor =
20287        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20288    editor
20289        .update(cx, |editor, _window, cx| {
20290            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
20291            editor
20292                .buffer
20293                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20294        })
20295        .unwrap();
20296
20297    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20298    cx.run_until_parked();
20299
20300    cx.update_editor(|editor, window, cx| {
20301        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20302    });
20303    cx.executor().run_until_parked();
20304
20305    // When the start of a hunk coincides with the start of its excerpt,
20306    // the hunk is expanded. When the start of a hunk is earlier than
20307    // the start of its excerpt, the hunk is not expanded.
20308    cx.assert_state_with_diff(
20309        "
20310            ˇaaa
20311          - bbb
20312          + BBB
20313
20314          - ddd
20315          - eee
20316          + DDD
20317          + EEE
20318            fff
20319
20320            iii
20321        "
20322        .unindent(),
20323    );
20324}
20325
20326#[gpui::test]
20327async fn test_edits_around_expanded_insertion_hunks(
20328    executor: BackgroundExecutor,
20329    cx: &mut TestAppContext,
20330) {
20331    init_test(cx, |_| {});
20332
20333    let mut cx = EditorTestContext::new(cx).await;
20334
20335    let diff_base = r#"
20336        use some::mod1;
20337        use some::mod2;
20338
20339        const A: u32 = 42;
20340
20341        fn main() {
20342            println!("hello");
20343
20344            println!("world");
20345        }
20346        "#
20347    .unindent();
20348    executor.run_until_parked();
20349    cx.set_state(
20350        &r#"
20351        use some::mod1;
20352        use some::mod2;
20353
20354        const A: u32 = 42;
20355        const B: u32 = 42;
20356        const C: u32 = 42;
20357        ˇ
20358
20359        fn main() {
20360            println!("hello");
20361
20362            println!("world");
20363        }
20364        "#
20365        .unindent(),
20366    );
20367
20368    cx.set_head_text(&diff_base);
20369    executor.run_until_parked();
20370
20371    cx.update_editor(|editor, window, cx| {
20372        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20373    });
20374    executor.run_until_parked();
20375
20376    cx.assert_state_with_diff(
20377        r#"
20378        use some::mod1;
20379        use some::mod2;
20380
20381        const A: u32 = 42;
20382      + const B: u32 = 42;
20383      + const C: u32 = 42;
20384      + ˇ
20385
20386        fn main() {
20387            println!("hello");
20388
20389            println!("world");
20390        }
20391      "#
20392        .unindent(),
20393    );
20394
20395    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20396    executor.run_until_parked();
20397
20398    cx.assert_state_with_diff(
20399        r#"
20400        use some::mod1;
20401        use some::mod2;
20402
20403        const A: u32 = 42;
20404      + const B: u32 = 42;
20405      + const C: u32 = 42;
20406      + const D: u32 = 42;
20407      + ˇ
20408
20409        fn main() {
20410            println!("hello");
20411
20412            println!("world");
20413        }
20414      "#
20415        .unindent(),
20416    );
20417
20418    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20419    executor.run_until_parked();
20420
20421    cx.assert_state_with_diff(
20422        r#"
20423        use some::mod1;
20424        use some::mod2;
20425
20426        const A: u32 = 42;
20427      + const B: u32 = 42;
20428      + const C: u32 = 42;
20429      + const D: u32 = 42;
20430      + const E: u32 = 42;
20431      + ˇ
20432
20433        fn main() {
20434            println!("hello");
20435
20436            println!("world");
20437        }
20438      "#
20439        .unindent(),
20440    );
20441
20442    cx.update_editor(|editor, window, cx| {
20443        editor.delete_line(&DeleteLine, window, cx);
20444    });
20445    executor.run_until_parked();
20446
20447    cx.assert_state_with_diff(
20448        r#"
20449        use some::mod1;
20450        use some::mod2;
20451
20452        const A: u32 = 42;
20453      + const B: u32 = 42;
20454      + const C: u32 = 42;
20455      + const D: u32 = 42;
20456      + const E: u32 = 42;
20457        ˇ
20458        fn main() {
20459            println!("hello");
20460
20461            println!("world");
20462        }
20463      "#
20464        .unindent(),
20465    );
20466
20467    cx.update_editor(|editor, window, cx| {
20468        editor.move_up(&MoveUp, window, cx);
20469        editor.delete_line(&DeleteLine, window, cx);
20470        editor.move_up(&MoveUp, window, cx);
20471        editor.delete_line(&DeleteLine, window, cx);
20472        editor.move_up(&MoveUp, window, cx);
20473        editor.delete_line(&DeleteLine, window, cx);
20474    });
20475    executor.run_until_parked();
20476    cx.assert_state_with_diff(
20477        r#"
20478        use some::mod1;
20479        use some::mod2;
20480
20481        const A: u32 = 42;
20482      + const B: u32 = 42;
20483        ˇ
20484        fn main() {
20485            println!("hello");
20486
20487            println!("world");
20488        }
20489      "#
20490        .unindent(),
20491    );
20492
20493    cx.update_editor(|editor, window, cx| {
20494        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20495        editor.delete_line(&DeleteLine, window, cx);
20496    });
20497    executor.run_until_parked();
20498    cx.assert_state_with_diff(
20499        r#"
20500        ˇ
20501        fn main() {
20502            println!("hello");
20503
20504            println!("world");
20505        }
20506      "#
20507        .unindent(),
20508    );
20509}
20510
20511#[gpui::test]
20512async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20513    init_test(cx, |_| {});
20514
20515    let mut cx = EditorTestContext::new(cx).await;
20516    cx.set_head_text(indoc! { "
20517        one
20518        two
20519        three
20520        four
20521        five
20522        "
20523    });
20524    cx.set_state(indoc! { "
20525        one
20526        ˇthree
20527        five
20528    "});
20529    cx.run_until_parked();
20530    cx.update_editor(|editor, window, cx| {
20531        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20532    });
20533    cx.assert_state_with_diff(
20534        indoc! { "
20535        one
20536      - two
20537        ˇthree
20538      - four
20539        five
20540    "}
20541        .to_string(),
20542    );
20543    cx.update_editor(|editor, window, cx| {
20544        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20545    });
20546
20547    cx.assert_state_with_diff(
20548        indoc! { "
20549        one
20550        ˇthree
20551        five
20552    "}
20553        .to_string(),
20554    );
20555
20556    cx.set_state(indoc! { "
20557        one
20558        ˇTWO
20559        three
20560        four
20561        five
20562    "});
20563    cx.run_until_parked();
20564    cx.update_editor(|editor, window, cx| {
20565        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20566    });
20567
20568    cx.assert_state_with_diff(
20569        indoc! { "
20570            one
20571          - two
20572          + ˇTWO
20573            three
20574            four
20575            five
20576        "}
20577        .to_string(),
20578    );
20579    cx.update_editor(|editor, window, cx| {
20580        editor.move_up(&Default::default(), window, cx);
20581        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20582    });
20583    cx.assert_state_with_diff(
20584        indoc! { "
20585            one
20586            ˇTWO
20587            three
20588            four
20589            five
20590        "}
20591        .to_string(),
20592    );
20593}
20594
20595#[gpui::test]
20596async fn test_edits_around_expanded_deletion_hunks(
20597    executor: BackgroundExecutor,
20598    cx: &mut TestAppContext,
20599) {
20600    init_test(cx, |_| {});
20601
20602    let mut cx = EditorTestContext::new(cx).await;
20603
20604    let diff_base = r#"
20605        use some::mod1;
20606        use some::mod2;
20607
20608        const A: u32 = 42;
20609        const B: u32 = 42;
20610        const C: u32 = 42;
20611
20612
20613        fn main() {
20614            println!("hello");
20615
20616            println!("world");
20617        }
20618    "#
20619    .unindent();
20620    executor.run_until_parked();
20621    cx.set_state(
20622        &r#"
20623        use some::mod1;
20624        use some::mod2;
20625
20626        ˇconst B: u32 = 42;
20627        const C: u32 = 42;
20628
20629
20630        fn main() {
20631            println!("hello");
20632
20633            println!("world");
20634        }
20635        "#
20636        .unindent(),
20637    );
20638
20639    cx.set_head_text(&diff_base);
20640    executor.run_until_parked();
20641
20642    cx.update_editor(|editor, window, cx| {
20643        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20644    });
20645    executor.run_until_parked();
20646
20647    cx.assert_state_with_diff(
20648        r#"
20649        use some::mod1;
20650        use some::mod2;
20651
20652      - const A: u32 = 42;
20653        ˇconst B: u32 = 42;
20654        const C: u32 = 42;
20655
20656
20657        fn main() {
20658            println!("hello");
20659
20660            println!("world");
20661        }
20662      "#
20663        .unindent(),
20664    );
20665
20666    cx.update_editor(|editor, window, cx| {
20667        editor.delete_line(&DeleteLine, window, cx);
20668    });
20669    executor.run_until_parked();
20670    cx.assert_state_with_diff(
20671        r#"
20672        use some::mod1;
20673        use some::mod2;
20674
20675      - const A: u32 = 42;
20676      - const B: u32 = 42;
20677        ˇconst C: u32 = 42;
20678
20679
20680        fn main() {
20681            println!("hello");
20682
20683            println!("world");
20684        }
20685      "#
20686        .unindent(),
20687    );
20688
20689    cx.update_editor(|editor, window, cx| {
20690        editor.delete_line(&DeleteLine, window, cx);
20691    });
20692    executor.run_until_parked();
20693    cx.assert_state_with_diff(
20694        r#"
20695        use some::mod1;
20696        use some::mod2;
20697
20698      - const A: u32 = 42;
20699      - const B: u32 = 42;
20700      - const C: u32 = 42;
20701        ˇ
20702
20703        fn main() {
20704            println!("hello");
20705
20706            println!("world");
20707        }
20708      "#
20709        .unindent(),
20710    );
20711
20712    cx.update_editor(|editor, window, cx| {
20713        editor.handle_input("replacement", window, cx);
20714    });
20715    executor.run_until_parked();
20716    cx.assert_state_with_diff(
20717        r#"
20718        use some::mod1;
20719        use some::mod2;
20720
20721      - const A: u32 = 42;
20722      - const B: u32 = 42;
20723      - const C: u32 = 42;
20724      -
20725      + replacementˇ
20726
20727        fn main() {
20728            println!("hello");
20729
20730            println!("world");
20731        }
20732      "#
20733        .unindent(),
20734    );
20735}
20736
20737#[gpui::test]
20738async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20739    init_test(cx, |_| {});
20740
20741    let mut cx = EditorTestContext::new(cx).await;
20742
20743    let base_text = r#"
20744        one
20745        two
20746        three
20747        four
20748        five
20749    "#
20750    .unindent();
20751    executor.run_until_parked();
20752    cx.set_state(
20753        &r#"
20754        one
20755        two
20756        fˇour
20757        five
20758        "#
20759        .unindent(),
20760    );
20761
20762    cx.set_head_text(&base_text);
20763    executor.run_until_parked();
20764
20765    cx.update_editor(|editor, window, cx| {
20766        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20767    });
20768    executor.run_until_parked();
20769
20770    cx.assert_state_with_diff(
20771        r#"
20772          one
20773          two
20774        - three
20775          fˇour
20776          five
20777        "#
20778        .unindent(),
20779    );
20780
20781    cx.update_editor(|editor, window, cx| {
20782        editor.backspace(&Backspace, window, cx);
20783        editor.backspace(&Backspace, window, cx);
20784    });
20785    executor.run_until_parked();
20786    cx.assert_state_with_diff(
20787        r#"
20788          one
20789          two
20790        - threeˇ
20791        - four
20792        + our
20793          five
20794        "#
20795        .unindent(),
20796    );
20797}
20798
20799#[gpui::test]
20800async fn test_edit_after_expanded_modification_hunk(
20801    executor: BackgroundExecutor,
20802    cx: &mut TestAppContext,
20803) {
20804    init_test(cx, |_| {});
20805
20806    let mut cx = EditorTestContext::new(cx).await;
20807
20808    let diff_base = r#"
20809        use some::mod1;
20810        use some::mod2;
20811
20812        const A: u32 = 42;
20813        const B: u32 = 42;
20814        const C: u32 = 42;
20815        const D: u32 = 42;
20816
20817
20818        fn main() {
20819            println!("hello");
20820
20821            println!("world");
20822        }"#
20823    .unindent();
20824
20825    cx.set_state(
20826        &r#"
20827        use some::mod1;
20828        use some::mod2;
20829
20830        const A: u32 = 42;
20831        const B: u32 = 42;
20832        const C: u32 = 43ˇ
20833        const D: u32 = 42;
20834
20835
20836        fn main() {
20837            println!("hello");
20838
20839            println!("world");
20840        }"#
20841        .unindent(),
20842    );
20843
20844    cx.set_head_text(&diff_base);
20845    executor.run_until_parked();
20846    cx.update_editor(|editor, window, cx| {
20847        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20848    });
20849    executor.run_until_parked();
20850
20851    cx.assert_state_with_diff(
20852        r#"
20853        use some::mod1;
20854        use some::mod2;
20855
20856        const A: u32 = 42;
20857        const B: u32 = 42;
20858      - const C: u32 = 42;
20859      + const C: u32 = 43ˇ
20860        const D: u32 = 42;
20861
20862
20863        fn main() {
20864            println!("hello");
20865
20866            println!("world");
20867        }"#
20868        .unindent(),
20869    );
20870
20871    cx.update_editor(|editor, window, cx| {
20872        editor.handle_input("\nnew_line\n", window, cx);
20873    });
20874    executor.run_until_parked();
20875
20876    cx.assert_state_with_diff(
20877        r#"
20878        use some::mod1;
20879        use some::mod2;
20880
20881        const A: u32 = 42;
20882        const B: u32 = 42;
20883      - const C: u32 = 42;
20884      + const C: u32 = 43
20885      + new_line
20886      + ˇ
20887        const D: u32 = 42;
20888
20889
20890        fn main() {
20891            println!("hello");
20892
20893            println!("world");
20894        }"#
20895        .unindent(),
20896    );
20897}
20898
20899#[gpui::test]
20900async fn test_stage_and_unstage_added_file_hunk(
20901    executor: BackgroundExecutor,
20902    cx: &mut TestAppContext,
20903) {
20904    init_test(cx, |_| {});
20905
20906    let mut cx = EditorTestContext::new(cx).await;
20907    cx.update_editor(|editor, _, cx| {
20908        editor.set_expand_all_diff_hunks(cx);
20909    });
20910
20911    let working_copy = r#"
20912            ˇfn main() {
20913                println!("hello, world!");
20914            }
20915        "#
20916    .unindent();
20917
20918    cx.set_state(&working_copy);
20919    executor.run_until_parked();
20920
20921    cx.assert_state_with_diff(
20922        r#"
20923            + ˇfn main() {
20924            +     println!("hello, world!");
20925            + }
20926        "#
20927        .unindent(),
20928    );
20929    cx.assert_index_text(None);
20930
20931    cx.update_editor(|editor, window, cx| {
20932        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20933    });
20934    executor.run_until_parked();
20935    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20936    cx.assert_state_with_diff(
20937        r#"
20938            + ˇfn main() {
20939            +     println!("hello, world!");
20940            + }
20941        "#
20942        .unindent(),
20943    );
20944
20945    cx.update_editor(|editor, window, cx| {
20946        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20947    });
20948    executor.run_until_parked();
20949    cx.assert_index_text(None);
20950}
20951
20952async fn setup_indent_guides_editor(
20953    text: &str,
20954    cx: &mut TestAppContext,
20955) -> (BufferId, EditorTestContext) {
20956    init_test(cx, |_| {});
20957
20958    let mut cx = EditorTestContext::new(cx).await;
20959
20960    let buffer_id = cx.update_editor(|editor, window, cx| {
20961        editor.set_text(text, window, cx);
20962        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20963
20964        buffer_ids[0]
20965    });
20966
20967    (buffer_id, cx)
20968}
20969
20970fn assert_indent_guides(
20971    range: Range<u32>,
20972    expected: Vec<IndentGuide>,
20973    active_indices: Option<Vec<usize>>,
20974    cx: &mut EditorTestContext,
20975) {
20976    let indent_guides = cx.update_editor(|editor, window, cx| {
20977        let snapshot = editor.snapshot(window, cx).display_snapshot;
20978        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20979            editor,
20980            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20981            true,
20982            &snapshot,
20983            cx,
20984        );
20985
20986        indent_guides.sort_by(|a, b| {
20987            a.depth.cmp(&b.depth).then(
20988                a.start_row
20989                    .cmp(&b.start_row)
20990                    .then(a.end_row.cmp(&b.end_row)),
20991            )
20992        });
20993        indent_guides
20994    });
20995
20996    if let Some(expected) = active_indices {
20997        let active_indices = cx.update_editor(|editor, window, cx| {
20998            let snapshot = editor.snapshot(window, cx).display_snapshot;
20999            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21000        });
21001
21002        assert_eq!(
21003            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21004            expected,
21005            "Active indent guide indices do not match"
21006        );
21007    }
21008
21009    assert_eq!(indent_guides, expected, "Indent guides do not match");
21010}
21011
21012fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21013    IndentGuide {
21014        buffer_id,
21015        start_row: MultiBufferRow(start_row),
21016        end_row: MultiBufferRow(end_row),
21017        depth,
21018        tab_size: 4,
21019        settings: IndentGuideSettings {
21020            enabled: true,
21021            line_width: 1,
21022            active_line_width: 1,
21023            coloring: IndentGuideColoring::default(),
21024            background_coloring: IndentGuideBackgroundColoring::default(),
21025        },
21026    }
21027}
21028
21029#[gpui::test]
21030async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21031    let (buffer_id, mut cx) = setup_indent_guides_editor(
21032        &"
21033        fn main() {
21034            let a = 1;
21035        }"
21036        .unindent(),
21037        cx,
21038    )
21039    .await;
21040
21041    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21042}
21043
21044#[gpui::test]
21045async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21046    let (buffer_id, mut cx) = setup_indent_guides_editor(
21047        &"
21048        fn main() {
21049            let a = 1;
21050            let b = 2;
21051        }"
21052        .unindent(),
21053        cx,
21054    )
21055    .await;
21056
21057    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21058}
21059
21060#[gpui::test]
21061async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21062    let (buffer_id, mut cx) = setup_indent_guides_editor(
21063        &"
21064        fn main() {
21065            let a = 1;
21066            if a == 3 {
21067                let b = 2;
21068            } else {
21069                let c = 3;
21070            }
21071        }"
21072        .unindent(),
21073        cx,
21074    )
21075    .await;
21076
21077    assert_indent_guides(
21078        0..8,
21079        vec![
21080            indent_guide(buffer_id, 1, 6, 0),
21081            indent_guide(buffer_id, 3, 3, 1),
21082            indent_guide(buffer_id, 5, 5, 1),
21083        ],
21084        None,
21085        &mut cx,
21086    );
21087}
21088
21089#[gpui::test]
21090async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21091    let (buffer_id, mut cx) = setup_indent_guides_editor(
21092        &"
21093        fn main() {
21094            let a = 1;
21095                let b = 2;
21096            let c = 3;
21097        }"
21098        .unindent(),
21099        cx,
21100    )
21101    .await;
21102
21103    assert_indent_guides(
21104        0..5,
21105        vec![
21106            indent_guide(buffer_id, 1, 3, 0),
21107            indent_guide(buffer_id, 2, 2, 1),
21108        ],
21109        None,
21110        &mut cx,
21111    );
21112}
21113
21114#[gpui::test]
21115async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21116    let (buffer_id, mut cx) = setup_indent_guides_editor(
21117        &"
21118        fn main() {
21119            let a = 1;
21120
21121            let c = 3;
21122        }"
21123        .unindent(),
21124        cx,
21125    )
21126    .await;
21127
21128    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21129}
21130
21131#[gpui::test]
21132async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21133    let (buffer_id, mut cx) = setup_indent_guides_editor(
21134        &"
21135        fn main() {
21136            let a = 1;
21137
21138            let c = 3;
21139
21140            if a == 3 {
21141                let b = 2;
21142            } else {
21143                let c = 3;
21144            }
21145        }"
21146        .unindent(),
21147        cx,
21148    )
21149    .await;
21150
21151    assert_indent_guides(
21152        0..11,
21153        vec![
21154            indent_guide(buffer_id, 1, 9, 0),
21155            indent_guide(buffer_id, 6, 6, 1),
21156            indent_guide(buffer_id, 8, 8, 1),
21157        ],
21158        None,
21159        &mut cx,
21160    );
21161}
21162
21163#[gpui::test]
21164async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
21165    let (buffer_id, mut cx) = setup_indent_guides_editor(
21166        &"
21167        fn main() {
21168            let a = 1;
21169
21170            let c = 3;
21171
21172            if a == 3 {
21173                let b = 2;
21174            } else {
21175                let c = 3;
21176            }
21177        }"
21178        .unindent(),
21179        cx,
21180    )
21181    .await;
21182
21183    assert_indent_guides(
21184        1..11,
21185        vec![
21186            indent_guide(buffer_id, 1, 9, 0),
21187            indent_guide(buffer_id, 6, 6, 1),
21188            indent_guide(buffer_id, 8, 8, 1),
21189        ],
21190        None,
21191        &mut cx,
21192    );
21193}
21194
21195#[gpui::test]
21196async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21197    let (buffer_id, mut cx) = setup_indent_guides_editor(
21198        &"
21199        fn main() {
21200            let a = 1;
21201
21202            let c = 3;
21203
21204            if a == 3 {
21205                let b = 2;
21206            } else {
21207                let c = 3;
21208            }
21209        }"
21210        .unindent(),
21211        cx,
21212    )
21213    .await;
21214
21215    assert_indent_guides(
21216        1..10,
21217        vec![
21218            indent_guide(buffer_id, 1, 9, 0),
21219            indent_guide(buffer_id, 6, 6, 1),
21220            indent_guide(buffer_id, 8, 8, 1),
21221        ],
21222        None,
21223        &mut cx,
21224    );
21225}
21226
21227#[gpui::test]
21228async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21229    let (buffer_id, mut cx) = setup_indent_guides_editor(
21230        &"
21231        fn main() {
21232            if a {
21233                b(
21234                    c,
21235                    d,
21236                )
21237            } else {
21238                e(
21239                    f
21240                )
21241            }
21242        }"
21243        .unindent(),
21244        cx,
21245    )
21246    .await;
21247
21248    assert_indent_guides(
21249        0..11,
21250        vec![
21251            indent_guide(buffer_id, 1, 10, 0),
21252            indent_guide(buffer_id, 2, 5, 1),
21253            indent_guide(buffer_id, 7, 9, 1),
21254            indent_guide(buffer_id, 3, 4, 2),
21255            indent_guide(buffer_id, 8, 8, 2),
21256        ],
21257        None,
21258        &mut cx,
21259    );
21260
21261    cx.update_editor(|editor, window, cx| {
21262        editor.fold_at(MultiBufferRow(2), window, cx);
21263        assert_eq!(
21264            editor.display_text(cx),
21265            "
21266            fn main() {
21267                if a {
21268                    b(⋯
21269                    )
21270                } else {
21271                    e(
21272                        f
21273                    )
21274                }
21275            }"
21276            .unindent()
21277        );
21278    });
21279
21280    assert_indent_guides(
21281        0..11,
21282        vec![
21283            indent_guide(buffer_id, 1, 10, 0),
21284            indent_guide(buffer_id, 2, 5, 1),
21285            indent_guide(buffer_id, 7, 9, 1),
21286            indent_guide(buffer_id, 8, 8, 2),
21287        ],
21288        None,
21289        &mut cx,
21290    );
21291}
21292
21293#[gpui::test]
21294async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21295    let (buffer_id, mut cx) = setup_indent_guides_editor(
21296        &"
21297        block1
21298            block2
21299                block3
21300                    block4
21301            block2
21302        block1
21303        block1"
21304            .unindent(),
21305        cx,
21306    )
21307    .await;
21308
21309    assert_indent_guides(
21310        1..10,
21311        vec![
21312            indent_guide(buffer_id, 1, 4, 0),
21313            indent_guide(buffer_id, 2, 3, 1),
21314            indent_guide(buffer_id, 3, 3, 2),
21315        ],
21316        None,
21317        &mut cx,
21318    );
21319}
21320
21321#[gpui::test]
21322async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21323    let (buffer_id, mut cx) = setup_indent_guides_editor(
21324        &"
21325        block1
21326            block2
21327                block3
21328
21329        block1
21330        block1"
21331            .unindent(),
21332        cx,
21333    )
21334    .await;
21335
21336    assert_indent_guides(
21337        0..6,
21338        vec![
21339            indent_guide(buffer_id, 1, 2, 0),
21340            indent_guide(buffer_id, 2, 2, 1),
21341        ],
21342        None,
21343        &mut cx,
21344    );
21345}
21346
21347#[gpui::test]
21348async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21349    let (buffer_id, mut cx) = setup_indent_guides_editor(
21350        &"
21351        function component() {
21352        \treturn (
21353        \t\t\t
21354        \t\t<div>
21355        \t\t\t<abc></abc>
21356        \t\t</div>
21357        \t)
21358        }"
21359        .unindent(),
21360        cx,
21361    )
21362    .await;
21363
21364    assert_indent_guides(
21365        0..8,
21366        vec![
21367            indent_guide(buffer_id, 1, 6, 0),
21368            indent_guide(buffer_id, 2, 5, 1),
21369            indent_guide(buffer_id, 4, 4, 2),
21370        ],
21371        None,
21372        &mut cx,
21373    );
21374}
21375
21376#[gpui::test]
21377async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21378    let (buffer_id, mut cx) = setup_indent_guides_editor(
21379        &"
21380        function component() {
21381        \treturn (
21382        \t
21383        \t\t<div>
21384        \t\t\t<abc></abc>
21385        \t\t</div>
21386        \t)
21387        }"
21388        .unindent(),
21389        cx,
21390    )
21391    .await;
21392
21393    assert_indent_guides(
21394        0..8,
21395        vec![
21396            indent_guide(buffer_id, 1, 6, 0),
21397            indent_guide(buffer_id, 2, 5, 1),
21398            indent_guide(buffer_id, 4, 4, 2),
21399        ],
21400        None,
21401        &mut cx,
21402    );
21403}
21404
21405#[gpui::test]
21406async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21407    let (buffer_id, mut cx) = setup_indent_guides_editor(
21408        &"
21409        block1
21410
21411
21412
21413            block2
21414        "
21415        .unindent(),
21416        cx,
21417    )
21418    .await;
21419
21420    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21421}
21422
21423#[gpui::test]
21424async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21425    let (buffer_id, mut cx) = setup_indent_guides_editor(
21426        &"
21427        def a:
21428        \tb = 3
21429        \tif True:
21430        \t\tc = 4
21431        \t\td = 5
21432        \tprint(b)
21433        "
21434        .unindent(),
21435        cx,
21436    )
21437    .await;
21438
21439    assert_indent_guides(
21440        0..6,
21441        vec![
21442            indent_guide(buffer_id, 1, 5, 0),
21443            indent_guide(buffer_id, 3, 4, 1),
21444        ],
21445        None,
21446        &mut cx,
21447    );
21448}
21449
21450#[gpui::test]
21451async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21452    let (buffer_id, mut cx) = setup_indent_guides_editor(
21453        &"
21454    fn main() {
21455        let a = 1;
21456    }"
21457        .unindent(),
21458        cx,
21459    )
21460    .await;
21461
21462    cx.update_editor(|editor, window, cx| {
21463        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21464            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21465        });
21466    });
21467
21468    assert_indent_guides(
21469        0..3,
21470        vec![indent_guide(buffer_id, 1, 1, 0)],
21471        Some(vec![0]),
21472        &mut cx,
21473    );
21474}
21475
21476#[gpui::test]
21477async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21478    let (buffer_id, mut cx) = setup_indent_guides_editor(
21479        &"
21480    fn main() {
21481        if 1 == 2 {
21482            let a = 1;
21483        }
21484    }"
21485        .unindent(),
21486        cx,
21487    )
21488    .await;
21489
21490    cx.update_editor(|editor, window, cx| {
21491        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21492            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21493        });
21494    });
21495
21496    assert_indent_guides(
21497        0..4,
21498        vec![
21499            indent_guide(buffer_id, 1, 3, 0),
21500            indent_guide(buffer_id, 2, 2, 1),
21501        ],
21502        Some(vec![1]),
21503        &mut cx,
21504    );
21505
21506    cx.update_editor(|editor, window, cx| {
21507        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21508            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21509        });
21510    });
21511
21512    assert_indent_guides(
21513        0..4,
21514        vec![
21515            indent_guide(buffer_id, 1, 3, 0),
21516            indent_guide(buffer_id, 2, 2, 1),
21517        ],
21518        Some(vec![1]),
21519        &mut cx,
21520    );
21521
21522    cx.update_editor(|editor, window, cx| {
21523        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21524            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21525        });
21526    });
21527
21528    assert_indent_guides(
21529        0..4,
21530        vec![
21531            indent_guide(buffer_id, 1, 3, 0),
21532            indent_guide(buffer_id, 2, 2, 1),
21533        ],
21534        Some(vec![0]),
21535        &mut cx,
21536    );
21537}
21538
21539#[gpui::test]
21540async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21541    let (buffer_id, mut cx) = setup_indent_guides_editor(
21542        &"
21543    fn main() {
21544        let a = 1;
21545
21546        let b = 2;
21547    }"
21548        .unindent(),
21549        cx,
21550    )
21551    .await;
21552
21553    cx.update_editor(|editor, window, cx| {
21554        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21555            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21556        });
21557    });
21558
21559    assert_indent_guides(
21560        0..5,
21561        vec![indent_guide(buffer_id, 1, 3, 0)],
21562        Some(vec![0]),
21563        &mut cx,
21564    );
21565}
21566
21567#[gpui::test]
21568async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21569    let (buffer_id, mut cx) = setup_indent_guides_editor(
21570        &"
21571    def m:
21572        a = 1
21573        pass"
21574            .unindent(),
21575        cx,
21576    )
21577    .await;
21578
21579    cx.update_editor(|editor, window, cx| {
21580        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21581            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21582        });
21583    });
21584
21585    assert_indent_guides(
21586        0..3,
21587        vec![indent_guide(buffer_id, 1, 2, 0)],
21588        Some(vec![0]),
21589        &mut cx,
21590    );
21591}
21592
21593#[gpui::test]
21594async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21595    init_test(cx, |_| {});
21596    let mut cx = EditorTestContext::new(cx).await;
21597    let text = indoc! {
21598        "
21599        impl A {
21600            fn b() {
21601                0;
21602                3;
21603                5;
21604                6;
21605                7;
21606            }
21607        }
21608        "
21609    };
21610    let base_text = indoc! {
21611        "
21612        impl A {
21613            fn b() {
21614                0;
21615                1;
21616                2;
21617                3;
21618                4;
21619            }
21620            fn c() {
21621                5;
21622                6;
21623                7;
21624            }
21625        }
21626        "
21627    };
21628
21629    cx.update_editor(|editor, window, cx| {
21630        editor.set_text(text, window, cx);
21631
21632        editor.buffer().update(cx, |multibuffer, cx| {
21633            let buffer = multibuffer.as_singleton().unwrap();
21634            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21635
21636            multibuffer.set_all_diff_hunks_expanded(cx);
21637            multibuffer.add_diff(diff, cx);
21638
21639            buffer.read(cx).remote_id()
21640        })
21641    });
21642    cx.run_until_parked();
21643
21644    cx.assert_state_with_diff(
21645        indoc! { "
21646          impl A {
21647              fn b() {
21648                  0;
21649        -         1;
21650        -         2;
21651                  3;
21652        -         4;
21653        -     }
21654        -     fn c() {
21655                  5;
21656                  6;
21657                  7;
21658              }
21659          }
21660          ˇ"
21661        }
21662        .to_string(),
21663    );
21664
21665    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21666        editor
21667            .snapshot(window, cx)
21668            .buffer_snapshot()
21669            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21670            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21671            .collect::<Vec<_>>()
21672    });
21673    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21674    assert_eq!(
21675        actual_guides,
21676        vec![
21677            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21678            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21679            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21680        ]
21681    );
21682}
21683
21684#[gpui::test]
21685async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21686    init_test(cx, |_| {});
21687    let mut cx = EditorTestContext::new(cx).await;
21688
21689    let diff_base = r#"
21690        a
21691        b
21692        c
21693        "#
21694    .unindent();
21695
21696    cx.set_state(
21697        &r#"
21698        ˇA
21699        b
21700        C
21701        "#
21702        .unindent(),
21703    );
21704    cx.set_head_text(&diff_base);
21705    cx.update_editor(|editor, window, cx| {
21706        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21707    });
21708    executor.run_until_parked();
21709
21710    let both_hunks_expanded = r#"
21711        - a
21712        + ˇA
21713          b
21714        - c
21715        + C
21716        "#
21717    .unindent();
21718
21719    cx.assert_state_with_diff(both_hunks_expanded.clone());
21720
21721    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21722        let snapshot = editor.snapshot(window, cx);
21723        let hunks = editor
21724            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21725            .collect::<Vec<_>>();
21726        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21727        hunks
21728            .into_iter()
21729            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21730            .collect::<Vec<_>>()
21731    });
21732    assert_eq!(hunk_ranges.len(), 2);
21733
21734    cx.update_editor(|editor, _, cx| {
21735        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21736    });
21737    executor.run_until_parked();
21738
21739    let second_hunk_expanded = r#"
21740          ˇA
21741          b
21742        - c
21743        + C
21744        "#
21745    .unindent();
21746
21747    cx.assert_state_with_diff(second_hunk_expanded);
21748
21749    cx.update_editor(|editor, _, cx| {
21750        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21751    });
21752    executor.run_until_parked();
21753
21754    cx.assert_state_with_diff(both_hunks_expanded.clone());
21755
21756    cx.update_editor(|editor, _, cx| {
21757        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21758    });
21759    executor.run_until_parked();
21760
21761    let first_hunk_expanded = r#"
21762        - a
21763        + ˇA
21764          b
21765          C
21766        "#
21767    .unindent();
21768
21769    cx.assert_state_with_diff(first_hunk_expanded);
21770
21771    cx.update_editor(|editor, _, cx| {
21772        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21773    });
21774    executor.run_until_parked();
21775
21776    cx.assert_state_with_diff(both_hunks_expanded);
21777
21778    cx.set_state(
21779        &r#"
21780        ˇA
21781        b
21782        "#
21783        .unindent(),
21784    );
21785    cx.run_until_parked();
21786
21787    // TODO this cursor position seems bad
21788    cx.assert_state_with_diff(
21789        r#"
21790        - ˇa
21791        + A
21792          b
21793        "#
21794        .unindent(),
21795    );
21796
21797    cx.update_editor(|editor, window, cx| {
21798        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21799    });
21800
21801    cx.assert_state_with_diff(
21802        r#"
21803            - ˇa
21804            + A
21805              b
21806            - c
21807            "#
21808        .unindent(),
21809    );
21810
21811    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21812        let snapshot = editor.snapshot(window, cx);
21813        let hunks = editor
21814            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21815            .collect::<Vec<_>>();
21816        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21817        hunks
21818            .into_iter()
21819            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21820            .collect::<Vec<_>>()
21821    });
21822    assert_eq!(hunk_ranges.len(), 2);
21823
21824    cx.update_editor(|editor, _, cx| {
21825        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21826    });
21827    executor.run_until_parked();
21828
21829    cx.assert_state_with_diff(
21830        r#"
21831        - ˇa
21832        + A
21833          b
21834        "#
21835        .unindent(),
21836    );
21837}
21838
21839#[gpui::test]
21840async fn test_toggle_deletion_hunk_at_start_of_file(
21841    executor: BackgroundExecutor,
21842    cx: &mut TestAppContext,
21843) {
21844    init_test(cx, |_| {});
21845    let mut cx = EditorTestContext::new(cx).await;
21846
21847    let diff_base = r#"
21848        a
21849        b
21850        c
21851        "#
21852    .unindent();
21853
21854    cx.set_state(
21855        &r#"
21856        ˇb
21857        c
21858        "#
21859        .unindent(),
21860    );
21861    cx.set_head_text(&diff_base);
21862    cx.update_editor(|editor, window, cx| {
21863        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21864    });
21865    executor.run_until_parked();
21866
21867    let hunk_expanded = r#"
21868        - a
21869          ˇb
21870          c
21871        "#
21872    .unindent();
21873
21874    cx.assert_state_with_diff(hunk_expanded.clone());
21875
21876    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21877        let snapshot = editor.snapshot(window, cx);
21878        let hunks = editor
21879            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21880            .collect::<Vec<_>>();
21881        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21882        hunks
21883            .into_iter()
21884            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21885            .collect::<Vec<_>>()
21886    });
21887    assert_eq!(hunk_ranges.len(), 1);
21888
21889    cx.update_editor(|editor, _, cx| {
21890        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21891    });
21892    executor.run_until_parked();
21893
21894    let hunk_collapsed = r#"
21895          ˇb
21896          c
21897        "#
21898    .unindent();
21899
21900    cx.assert_state_with_diff(hunk_collapsed);
21901
21902    cx.update_editor(|editor, _, cx| {
21903        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21904    });
21905    executor.run_until_parked();
21906
21907    cx.assert_state_with_diff(hunk_expanded);
21908}
21909
21910#[gpui::test]
21911async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21912    init_test(cx, |_| {});
21913
21914    let fs = FakeFs::new(cx.executor());
21915    fs.insert_tree(
21916        path!("/test"),
21917        json!({
21918            ".git": {},
21919            "file-1": "ONE\n",
21920            "file-2": "TWO\n",
21921            "file-3": "THREE\n",
21922        }),
21923    )
21924    .await;
21925
21926    fs.set_head_for_repo(
21927        path!("/test/.git").as_ref(),
21928        &[
21929            ("file-1", "one\n".into()),
21930            ("file-2", "two\n".into()),
21931            ("file-3", "three\n".into()),
21932        ],
21933        "deadbeef",
21934    );
21935
21936    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21937    let mut buffers = vec![];
21938    for i in 1..=3 {
21939        let buffer = project
21940            .update(cx, |project, cx| {
21941                let path = format!(path!("/test/file-{}"), i);
21942                project.open_local_buffer(path, cx)
21943            })
21944            .await
21945            .unwrap();
21946        buffers.push(buffer);
21947    }
21948
21949    let multibuffer = cx.new(|cx| {
21950        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21951        multibuffer.set_all_diff_hunks_expanded(cx);
21952        for buffer in &buffers {
21953            let snapshot = buffer.read(cx).snapshot();
21954            multibuffer.set_excerpts_for_path(
21955                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21956                buffer.clone(),
21957                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21958                2,
21959                cx,
21960            );
21961        }
21962        multibuffer
21963    });
21964
21965    let editor = cx.add_window(|window, cx| {
21966        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21967    });
21968    cx.run_until_parked();
21969
21970    let snapshot = editor
21971        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21972        .unwrap();
21973    let hunks = snapshot
21974        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21975        .map(|hunk| match hunk {
21976            DisplayDiffHunk::Unfolded {
21977                display_row_range, ..
21978            } => display_row_range,
21979            DisplayDiffHunk::Folded { .. } => unreachable!(),
21980        })
21981        .collect::<Vec<_>>();
21982    assert_eq!(
21983        hunks,
21984        [
21985            DisplayRow(2)..DisplayRow(4),
21986            DisplayRow(7)..DisplayRow(9),
21987            DisplayRow(12)..DisplayRow(14),
21988        ]
21989    );
21990}
21991
21992#[gpui::test]
21993async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21994    init_test(cx, |_| {});
21995
21996    let mut cx = EditorTestContext::new(cx).await;
21997    cx.set_head_text(indoc! { "
21998        one
21999        two
22000        three
22001        four
22002        five
22003        "
22004    });
22005    cx.set_index_text(indoc! { "
22006        one
22007        two
22008        three
22009        four
22010        five
22011        "
22012    });
22013    cx.set_state(indoc! {"
22014        one
22015        TWO
22016        ˇTHREE
22017        FOUR
22018        five
22019    "});
22020    cx.run_until_parked();
22021    cx.update_editor(|editor, window, cx| {
22022        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22023    });
22024    cx.run_until_parked();
22025    cx.assert_index_text(Some(indoc! {"
22026        one
22027        TWO
22028        THREE
22029        FOUR
22030        five
22031    "}));
22032    cx.set_state(indoc! { "
22033        one
22034        TWO
22035        ˇTHREE-HUNDRED
22036        FOUR
22037        five
22038    "});
22039    cx.run_until_parked();
22040    cx.update_editor(|editor, window, cx| {
22041        let snapshot = editor.snapshot(window, cx);
22042        let hunks = editor
22043            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22044            .collect::<Vec<_>>();
22045        assert_eq!(hunks.len(), 1);
22046        assert_eq!(
22047            hunks[0].status(),
22048            DiffHunkStatus {
22049                kind: DiffHunkStatusKind::Modified,
22050                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22051            }
22052        );
22053
22054        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22055    });
22056    cx.run_until_parked();
22057    cx.assert_index_text(Some(indoc! {"
22058        one
22059        TWO
22060        THREE-HUNDRED
22061        FOUR
22062        five
22063    "}));
22064}
22065
22066#[gpui::test]
22067fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22068    init_test(cx, |_| {});
22069
22070    let editor = cx.add_window(|window, cx| {
22071        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22072        build_editor(buffer, window, cx)
22073    });
22074
22075    let render_args = Arc::new(Mutex::new(None));
22076    let snapshot = editor
22077        .update(cx, |editor, window, cx| {
22078            let snapshot = editor.buffer().read(cx).snapshot(cx);
22079            let range =
22080                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22081
22082            struct RenderArgs {
22083                row: MultiBufferRow,
22084                folded: bool,
22085                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22086            }
22087
22088            let crease = Crease::inline(
22089                range,
22090                FoldPlaceholder::test(),
22091                {
22092                    let toggle_callback = render_args.clone();
22093                    move |row, folded, callback, _window, _cx| {
22094                        *toggle_callback.lock() = Some(RenderArgs {
22095                            row,
22096                            folded,
22097                            callback,
22098                        });
22099                        div()
22100                    }
22101                },
22102                |_row, _folded, _window, _cx| div(),
22103            );
22104
22105            editor.insert_creases(Some(crease), cx);
22106            let snapshot = editor.snapshot(window, cx);
22107            let _div =
22108                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22109            snapshot
22110        })
22111        .unwrap();
22112
22113    let render_args = render_args.lock().take().unwrap();
22114    assert_eq!(render_args.row, MultiBufferRow(1));
22115    assert!(!render_args.folded);
22116    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22117
22118    cx.update_window(*editor, |_, window, cx| {
22119        (render_args.callback)(true, window, cx)
22120    })
22121    .unwrap();
22122    let snapshot = editor
22123        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22124        .unwrap();
22125    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22126
22127    cx.update_window(*editor, |_, window, cx| {
22128        (render_args.callback)(false, window, cx)
22129    })
22130    .unwrap();
22131    let snapshot = editor
22132        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22133        .unwrap();
22134    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22135}
22136
22137#[gpui::test]
22138async fn test_input_text(cx: &mut TestAppContext) {
22139    init_test(cx, |_| {});
22140    let mut cx = EditorTestContext::new(cx).await;
22141
22142    cx.set_state(
22143        &r#"ˇone
22144        two
22145
22146        three
22147        fourˇ
22148        five
22149
22150        siˇx"#
22151            .unindent(),
22152    );
22153
22154    cx.dispatch_action(HandleInput(String::new()));
22155    cx.assert_editor_state(
22156        &r#"ˇone
22157        two
22158
22159        three
22160        fourˇ
22161        five
22162
22163        siˇx"#
22164            .unindent(),
22165    );
22166
22167    cx.dispatch_action(HandleInput("AAAA".to_string()));
22168    cx.assert_editor_state(
22169        &r#"AAAAˇone
22170        two
22171
22172        three
22173        fourAAAAˇ
22174        five
22175
22176        siAAAAˇx"#
22177            .unindent(),
22178    );
22179}
22180
22181#[gpui::test]
22182async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22183    init_test(cx, |_| {});
22184
22185    let mut cx = EditorTestContext::new(cx).await;
22186    cx.set_state(
22187        r#"let foo = 1;
22188let foo = 2;
22189let foo = 3;
22190let fooˇ = 4;
22191let foo = 5;
22192let foo = 6;
22193let foo = 7;
22194let foo = 8;
22195let foo = 9;
22196let foo = 10;
22197let foo = 11;
22198let foo = 12;
22199let foo = 13;
22200let foo = 14;
22201let foo = 15;"#,
22202    );
22203
22204    cx.update_editor(|e, window, cx| {
22205        assert_eq!(
22206            e.next_scroll_position,
22207            NextScrollCursorCenterTopBottom::Center,
22208            "Default next scroll direction is center",
22209        );
22210
22211        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22212        assert_eq!(
22213            e.next_scroll_position,
22214            NextScrollCursorCenterTopBottom::Top,
22215            "After center, next scroll direction should be top",
22216        );
22217
22218        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22219        assert_eq!(
22220            e.next_scroll_position,
22221            NextScrollCursorCenterTopBottom::Bottom,
22222            "After top, next scroll direction should be bottom",
22223        );
22224
22225        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22226        assert_eq!(
22227            e.next_scroll_position,
22228            NextScrollCursorCenterTopBottom::Center,
22229            "After bottom, scrolling should start over",
22230        );
22231
22232        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22233        assert_eq!(
22234            e.next_scroll_position,
22235            NextScrollCursorCenterTopBottom::Top,
22236            "Scrolling continues if retriggered fast enough"
22237        );
22238    });
22239
22240    cx.executor()
22241        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22242    cx.executor().run_until_parked();
22243    cx.update_editor(|e, _, _| {
22244        assert_eq!(
22245            e.next_scroll_position,
22246            NextScrollCursorCenterTopBottom::Center,
22247            "If scrolling is not triggered fast enough, it should reset"
22248        );
22249    });
22250}
22251
22252#[gpui::test]
22253async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22254    init_test(cx, |_| {});
22255    let mut cx = EditorLspTestContext::new_rust(
22256        lsp::ServerCapabilities {
22257            definition_provider: Some(lsp::OneOf::Left(true)),
22258            references_provider: Some(lsp::OneOf::Left(true)),
22259            ..lsp::ServerCapabilities::default()
22260        },
22261        cx,
22262    )
22263    .await;
22264
22265    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22266        let go_to_definition = cx
22267            .lsp
22268            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22269                move |params, _| async move {
22270                    if empty_go_to_definition {
22271                        Ok(None)
22272                    } else {
22273                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22274                            uri: params.text_document_position_params.text_document.uri,
22275                            range: lsp::Range::new(
22276                                lsp::Position::new(4, 3),
22277                                lsp::Position::new(4, 6),
22278                            ),
22279                        })))
22280                    }
22281                },
22282            );
22283        let references = cx
22284            .lsp
22285            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22286                Ok(Some(vec![lsp::Location {
22287                    uri: params.text_document_position.text_document.uri,
22288                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22289                }]))
22290            });
22291        (go_to_definition, references)
22292    };
22293
22294    cx.set_state(
22295        &r#"fn one() {
22296            let mut a = ˇtwo();
22297        }
22298
22299        fn two() {}"#
22300            .unindent(),
22301    );
22302    set_up_lsp_handlers(false, &mut cx);
22303    let navigated = cx
22304        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22305        .await
22306        .expect("Failed to navigate to definition");
22307    assert_eq!(
22308        navigated,
22309        Navigated::Yes,
22310        "Should have navigated to definition from the GetDefinition response"
22311    );
22312    cx.assert_editor_state(
22313        &r#"fn one() {
22314            let mut a = two();
22315        }
22316
22317        fn «twoˇ»() {}"#
22318            .unindent(),
22319    );
22320
22321    let editors = cx.update_workspace(|workspace, _, cx| {
22322        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22323    });
22324    cx.update_editor(|_, _, test_editor_cx| {
22325        assert_eq!(
22326            editors.len(),
22327            1,
22328            "Initially, only one, test, editor should be open in the workspace"
22329        );
22330        assert_eq!(
22331            test_editor_cx.entity(),
22332            editors.last().expect("Asserted len is 1").clone()
22333        );
22334    });
22335
22336    set_up_lsp_handlers(true, &mut cx);
22337    let navigated = cx
22338        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22339        .await
22340        .expect("Failed to navigate to lookup references");
22341    assert_eq!(
22342        navigated,
22343        Navigated::Yes,
22344        "Should have navigated to references as a fallback after empty GoToDefinition response"
22345    );
22346    // We should not change the selections in the existing file,
22347    // if opening another milti buffer with the references
22348    cx.assert_editor_state(
22349        &r#"fn one() {
22350            let mut a = two();
22351        }
22352
22353        fn «twoˇ»() {}"#
22354            .unindent(),
22355    );
22356    let editors = cx.update_workspace(|workspace, _, cx| {
22357        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22358    });
22359    cx.update_editor(|_, _, test_editor_cx| {
22360        assert_eq!(
22361            editors.len(),
22362            2,
22363            "After falling back to references search, we open a new editor with the results"
22364        );
22365        let references_fallback_text = editors
22366            .into_iter()
22367            .find(|new_editor| *new_editor != test_editor_cx.entity())
22368            .expect("Should have one non-test editor now")
22369            .read(test_editor_cx)
22370            .text(test_editor_cx);
22371        assert_eq!(
22372            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22373            "Should use the range from the references response and not the GoToDefinition one"
22374        );
22375    });
22376}
22377
22378#[gpui::test]
22379async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22380    init_test(cx, |_| {});
22381    cx.update(|cx| {
22382        let mut editor_settings = EditorSettings::get_global(cx).clone();
22383        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22384        EditorSettings::override_global(editor_settings, cx);
22385    });
22386    let mut cx = EditorLspTestContext::new_rust(
22387        lsp::ServerCapabilities {
22388            definition_provider: Some(lsp::OneOf::Left(true)),
22389            references_provider: Some(lsp::OneOf::Left(true)),
22390            ..lsp::ServerCapabilities::default()
22391        },
22392        cx,
22393    )
22394    .await;
22395    let original_state = r#"fn one() {
22396        let mut a = ˇtwo();
22397    }
22398
22399    fn two() {}"#
22400        .unindent();
22401    cx.set_state(&original_state);
22402
22403    let mut go_to_definition = cx
22404        .lsp
22405        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22406            move |_, _| async move { Ok(None) },
22407        );
22408    let _references = cx
22409        .lsp
22410        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22411            panic!("Should not call for references with no go to definition fallback")
22412        });
22413
22414    let navigated = cx
22415        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22416        .await
22417        .expect("Failed to navigate to lookup references");
22418    go_to_definition
22419        .next()
22420        .await
22421        .expect("Should have called the go_to_definition handler");
22422
22423    assert_eq!(
22424        navigated,
22425        Navigated::No,
22426        "Should have navigated to references as a fallback after empty GoToDefinition response"
22427    );
22428    cx.assert_editor_state(&original_state);
22429    let editors = cx.update_workspace(|workspace, _, cx| {
22430        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22431    });
22432    cx.update_editor(|_, _, _| {
22433        assert_eq!(
22434            editors.len(),
22435            1,
22436            "After unsuccessful fallback, no other editor should have been opened"
22437        );
22438    });
22439}
22440
22441#[gpui::test]
22442async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22443    init_test(cx, |_| {});
22444    let mut cx = EditorLspTestContext::new_rust(
22445        lsp::ServerCapabilities {
22446            references_provider: Some(lsp::OneOf::Left(true)),
22447            ..lsp::ServerCapabilities::default()
22448        },
22449        cx,
22450    )
22451    .await;
22452
22453    cx.set_state(
22454        &r#"
22455        fn one() {
22456            let mut a = two();
22457        }
22458
22459        fn ˇtwo() {}"#
22460            .unindent(),
22461    );
22462    cx.lsp
22463        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22464            Ok(Some(vec![
22465                lsp::Location {
22466                    uri: params.text_document_position.text_document.uri.clone(),
22467                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22468                },
22469                lsp::Location {
22470                    uri: params.text_document_position.text_document.uri,
22471                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22472                },
22473            ]))
22474        });
22475    let navigated = cx
22476        .update_editor(|editor, window, cx| {
22477            editor.find_all_references(&FindAllReferences, window, cx)
22478        })
22479        .unwrap()
22480        .await
22481        .expect("Failed to navigate to references");
22482    assert_eq!(
22483        navigated,
22484        Navigated::Yes,
22485        "Should have navigated to references from the FindAllReferences response"
22486    );
22487    cx.assert_editor_state(
22488        &r#"fn one() {
22489            let mut a = two();
22490        }
22491
22492        fn ˇtwo() {}"#
22493            .unindent(),
22494    );
22495
22496    let editors = cx.update_workspace(|workspace, _, cx| {
22497        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22498    });
22499    cx.update_editor(|_, _, _| {
22500        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22501    });
22502
22503    cx.set_state(
22504        &r#"fn one() {
22505            let mut a = ˇtwo();
22506        }
22507
22508        fn two() {}"#
22509            .unindent(),
22510    );
22511    let navigated = cx
22512        .update_editor(|editor, window, cx| {
22513            editor.find_all_references(&FindAllReferences, window, cx)
22514        })
22515        .unwrap()
22516        .await
22517        .expect("Failed to navigate to references");
22518    assert_eq!(
22519        navigated,
22520        Navigated::Yes,
22521        "Should have navigated to references from the FindAllReferences response"
22522    );
22523    cx.assert_editor_state(
22524        &r#"fn one() {
22525            let mut a = ˇtwo();
22526        }
22527
22528        fn two() {}"#
22529            .unindent(),
22530    );
22531    let editors = cx.update_workspace(|workspace, _, cx| {
22532        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22533    });
22534    cx.update_editor(|_, _, _| {
22535        assert_eq!(
22536            editors.len(),
22537            2,
22538            "should have re-used the previous multibuffer"
22539        );
22540    });
22541
22542    cx.set_state(
22543        &r#"fn one() {
22544            let mut a = ˇtwo();
22545        }
22546        fn three() {}
22547        fn two() {}"#
22548            .unindent(),
22549    );
22550    cx.lsp
22551        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22552            Ok(Some(vec![
22553                lsp::Location {
22554                    uri: params.text_document_position.text_document.uri.clone(),
22555                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22556                },
22557                lsp::Location {
22558                    uri: params.text_document_position.text_document.uri,
22559                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22560                },
22561            ]))
22562        });
22563    let navigated = cx
22564        .update_editor(|editor, window, cx| {
22565            editor.find_all_references(&FindAllReferences, window, cx)
22566        })
22567        .unwrap()
22568        .await
22569        .expect("Failed to navigate to references");
22570    assert_eq!(
22571        navigated,
22572        Navigated::Yes,
22573        "Should have navigated to references from the FindAllReferences response"
22574    );
22575    cx.assert_editor_state(
22576        &r#"fn one() {
22577                let mut a = ˇtwo();
22578            }
22579            fn three() {}
22580            fn two() {}"#
22581            .unindent(),
22582    );
22583    let editors = cx.update_workspace(|workspace, _, cx| {
22584        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22585    });
22586    cx.update_editor(|_, _, _| {
22587        assert_eq!(
22588            editors.len(),
22589            3,
22590            "should have used a new multibuffer as offsets changed"
22591        );
22592    });
22593}
22594#[gpui::test]
22595async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22596    init_test(cx, |_| {});
22597
22598    let language = Arc::new(Language::new(
22599        LanguageConfig::default(),
22600        Some(tree_sitter_rust::LANGUAGE.into()),
22601    ));
22602
22603    let text = r#"
22604        #[cfg(test)]
22605        mod tests() {
22606            #[test]
22607            fn runnable_1() {
22608                let a = 1;
22609            }
22610
22611            #[test]
22612            fn runnable_2() {
22613                let a = 1;
22614                let b = 2;
22615            }
22616        }
22617    "#
22618    .unindent();
22619
22620    let fs = FakeFs::new(cx.executor());
22621    fs.insert_file("/file.rs", Default::default()).await;
22622
22623    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22624    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22625    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22626    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22627    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22628
22629    let editor = cx.new_window_entity(|window, cx| {
22630        Editor::new(
22631            EditorMode::full(),
22632            multi_buffer,
22633            Some(project.clone()),
22634            window,
22635            cx,
22636        )
22637    });
22638
22639    editor.update_in(cx, |editor, window, cx| {
22640        let snapshot = editor.buffer().read(cx).snapshot(cx);
22641        editor.tasks.insert(
22642            (buffer.read(cx).remote_id(), 3),
22643            RunnableTasks {
22644                templates: vec![],
22645                offset: snapshot.anchor_before(MultiBufferOffset(43)),
22646                column: 0,
22647                extra_variables: HashMap::default(),
22648                context_range: BufferOffset(43)..BufferOffset(85),
22649            },
22650        );
22651        editor.tasks.insert(
22652            (buffer.read(cx).remote_id(), 8),
22653            RunnableTasks {
22654                templates: vec![],
22655                offset: snapshot.anchor_before(MultiBufferOffset(86)),
22656                column: 0,
22657                extra_variables: HashMap::default(),
22658                context_range: BufferOffset(86)..BufferOffset(191),
22659            },
22660        );
22661
22662        // Test finding task when cursor is inside function body
22663        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22664            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22665        });
22666        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22667        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22668
22669        // Test finding task when cursor is on function name
22670        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22671            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22672        });
22673        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22674        assert_eq!(row, 8, "Should find task when cursor is on function name");
22675    });
22676}
22677
22678#[gpui::test]
22679async fn test_folding_buffers(cx: &mut TestAppContext) {
22680    init_test(cx, |_| {});
22681
22682    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22683    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22684    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22685
22686    let fs = FakeFs::new(cx.executor());
22687    fs.insert_tree(
22688        path!("/a"),
22689        json!({
22690            "first.rs": sample_text_1,
22691            "second.rs": sample_text_2,
22692            "third.rs": sample_text_3,
22693        }),
22694    )
22695    .await;
22696    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22697    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22698    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22699    let worktree = project.update(cx, |project, cx| {
22700        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22701        assert_eq!(worktrees.len(), 1);
22702        worktrees.pop().unwrap()
22703    });
22704    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22705
22706    let buffer_1 = project
22707        .update(cx, |project, cx| {
22708            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22709        })
22710        .await
22711        .unwrap();
22712    let buffer_2 = project
22713        .update(cx, |project, cx| {
22714            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22715        })
22716        .await
22717        .unwrap();
22718    let buffer_3 = project
22719        .update(cx, |project, cx| {
22720            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22721        })
22722        .await
22723        .unwrap();
22724
22725    let multi_buffer = cx.new(|cx| {
22726        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22727        multi_buffer.push_excerpts(
22728            buffer_1.clone(),
22729            [
22730                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22731                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22732                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22733            ],
22734            cx,
22735        );
22736        multi_buffer.push_excerpts(
22737            buffer_2.clone(),
22738            [
22739                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22740                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22741                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22742            ],
22743            cx,
22744        );
22745        multi_buffer.push_excerpts(
22746            buffer_3.clone(),
22747            [
22748                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22749                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22750                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22751            ],
22752            cx,
22753        );
22754        multi_buffer
22755    });
22756    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22757        Editor::new(
22758            EditorMode::full(),
22759            multi_buffer.clone(),
22760            Some(project.clone()),
22761            window,
22762            cx,
22763        )
22764    });
22765
22766    assert_eq!(
22767        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22768        "\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",
22769    );
22770
22771    multi_buffer_editor.update(cx, |editor, cx| {
22772        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22773    });
22774    assert_eq!(
22775        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22776        "\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",
22777        "After folding the first buffer, its text should not be displayed"
22778    );
22779
22780    multi_buffer_editor.update(cx, |editor, cx| {
22781        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22782    });
22783    assert_eq!(
22784        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22785        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22786        "After folding the second buffer, its text should not be displayed"
22787    );
22788
22789    multi_buffer_editor.update(cx, |editor, cx| {
22790        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22791    });
22792    assert_eq!(
22793        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22794        "\n\n\n\n\n",
22795        "After folding the third buffer, its text should not be displayed"
22796    );
22797
22798    // Emulate selection inside the fold logic, that should work
22799    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22800        editor
22801            .snapshot(window, cx)
22802            .next_line_boundary(Point::new(0, 4));
22803    });
22804
22805    multi_buffer_editor.update(cx, |editor, cx| {
22806        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22807    });
22808    assert_eq!(
22809        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22810        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22811        "After unfolding the second buffer, its text should be displayed"
22812    );
22813
22814    // Typing inside of buffer 1 causes that buffer to be unfolded.
22815    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22816        assert_eq!(
22817            multi_buffer
22818                .read(cx)
22819                .snapshot(cx)
22820                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22821                .collect::<String>(),
22822            "bbbb"
22823        );
22824        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22825            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22826        });
22827        editor.handle_input("B", window, cx);
22828    });
22829
22830    assert_eq!(
22831        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22832        "\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",
22833        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22834    );
22835
22836    multi_buffer_editor.update(cx, |editor, cx| {
22837        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22838    });
22839    assert_eq!(
22840        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22841        "\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",
22842        "After unfolding the all buffers, all original text should be displayed"
22843    );
22844}
22845
22846#[gpui::test]
22847async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22848    init_test(cx, |_| {});
22849
22850    let sample_text_1 = "1111\n2222\n3333".to_string();
22851    let sample_text_2 = "4444\n5555\n6666".to_string();
22852    let sample_text_3 = "7777\n8888\n9999".to_string();
22853
22854    let fs = FakeFs::new(cx.executor());
22855    fs.insert_tree(
22856        path!("/a"),
22857        json!({
22858            "first.rs": sample_text_1,
22859            "second.rs": sample_text_2,
22860            "third.rs": sample_text_3,
22861        }),
22862    )
22863    .await;
22864    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22865    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22866    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22867    let worktree = project.update(cx, |project, cx| {
22868        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22869        assert_eq!(worktrees.len(), 1);
22870        worktrees.pop().unwrap()
22871    });
22872    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22873
22874    let buffer_1 = project
22875        .update(cx, |project, cx| {
22876            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22877        })
22878        .await
22879        .unwrap();
22880    let buffer_2 = project
22881        .update(cx, |project, cx| {
22882            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22883        })
22884        .await
22885        .unwrap();
22886    let buffer_3 = project
22887        .update(cx, |project, cx| {
22888            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22889        })
22890        .await
22891        .unwrap();
22892
22893    let multi_buffer = cx.new(|cx| {
22894        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22895        multi_buffer.push_excerpts(
22896            buffer_1.clone(),
22897            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22898            cx,
22899        );
22900        multi_buffer.push_excerpts(
22901            buffer_2.clone(),
22902            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22903            cx,
22904        );
22905        multi_buffer.push_excerpts(
22906            buffer_3.clone(),
22907            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22908            cx,
22909        );
22910        multi_buffer
22911    });
22912
22913    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22914        Editor::new(
22915            EditorMode::full(),
22916            multi_buffer,
22917            Some(project.clone()),
22918            window,
22919            cx,
22920        )
22921    });
22922
22923    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22924    assert_eq!(
22925        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22926        full_text,
22927    );
22928
22929    multi_buffer_editor.update(cx, |editor, cx| {
22930        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22931    });
22932    assert_eq!(
22933        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22934        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22935        "After folding the first buffer, its text should not be displayed"
22936    );
22937
22938    multi_buffer_editor.update(cx, |editor, cx| {
22939        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22940    });
22941
22942    assert_eq!(
22943        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22944        "\n\n\n\n\n\n7777\n8888\n9999",
22945        "After folding the second buffer, its text should not be displayed"
22946    );
22947
22948    multi_buffer_editor.update(cx, |editor, cx| {
22949        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22950    });
22951    assert_eq!(
22952        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22953        "\n\n\n\n\n",
22954        "After folding the third buffer, its text should not be displayed"
22955    );
22956
22957    multi_buffer_editor.update(cx, |editor, cx| {
22958        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22959    });
22960    assert_eq!(
22961        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22962        "\n\n\n\n4444\n5555\n6666\n\n",
22963        "After unfolding the second buffer, its text should be displayed"
22964    );
22965
22966    multi_buffer_editor.update(cx, |editor, cx| {
22967        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22968    });
22969    assert_eq!(
22970        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22971        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22972        "After unfolding the first buffer, its text should be displayed"
22973    );
22974
22975    multi_buffer_editor.update(cx, |editor, cx| {
22976        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22977    });
22978    assert_eq!(
22979        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22980        full_text,
22981        "After unfolding all buffers, all original text should be displayed"
22982    );
22983}
22984
22985#[gpui::test]
22986async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22987    init_test(cx, |_| {});
22988
22989    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22990
22991    let fs = FakeFs::new(cx.executor());
22992    fs.insert_tree(
22993        path!("/a"),
22994        json!({
22995            "main.rs": sample_text,
22996        }),
22997    )
22998    .await;
22999    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23000    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23001    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23002    let worktree = project.update(cx, |project, cx| {
23003        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23004        assert_eq!(worktrees.len(), 1);
23005        worktrees.pop().unwrap()
23006    });
23007    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23008
23009    let buffer_1 = project
23010        .update(cx, |project, cx| {
23011            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23012        })
23013        .await
23014        .unwrap();
23015
23016    let multi_buffer = cx.new(|cx| {
23017        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23018        multi_buffer.push_excerpts(
23019            buffer_1.clone(),
23020            [ExcerptRange::new(
23021                Point::new(0, 0)
23022                    ..Point::new(
23023                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23024                        0,
23025                    ),
23026            )],
23027            cx,
23028        );
23029        multi_buffer
23030    });
23031    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23032        Editor::new(
23033            EditorMode::full(),
23034            multi_buffer,
23035            Some(project.clone()),
23036            window,
23037            cx,
23038        )
23039    });
23040
23041    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23042    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23043        enum TestHighlight {}
23044        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23045        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23046        editor.highlight_text::<TestHighlight>(
23047            vec![highlight_range.clone()],
23048            HighlightStyle::color(Hsla::green()),
23049            cx,
23050        );
23051        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23052            s.select_ranges(Some(highlight_range))
23053        });
23054    });
23055
23056    let full_text = format!("\n\n{sample_text}");
23057    assert_eq!(
23058        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23059        full_text,
23060    );
23061}
23062
23063#[gpui::test]
23064async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23065    init_test(cx, |_| {});
23066    cx.update(|cx| {
23067        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23068            "keymaps/default-linux.json",
23069            cx,
23070        )
23071        .unwrap();
23072        cx.bind_keys(default_key_bindings);
23073    });
23074
23075    let (editor, cx) = cx.add_window_view(|window, cx| {
23076        let multi_buffer = MultiBuffer::build_multi(
23077            [
23078                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23079                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23080                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23081                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23082            ],
23083            cx,
23084        );
23085        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23086
23087        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23088        // fold all but the second buffer, so that we test navigating between two
23089        // adjacent folded buffers, as well as folded buffers at the start and
23090        // end the multibuffer
23091        editor.fold_buffer(buffer_ids[0], cx);
23092        editor.fold_buffer(buffer_ids[2], cx);
23093        editor.fold_buffer(buffer_ids[3], cx);
23094
23095        editor
23096    });
23097    cx.simulate_resize(size(px(1000.), px(1000.)));
23098
23099    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23100    cx.assert_excerpts_with_selections(indoc! {"
23101        [EXCERPT]
23102        ˇ[FOLDED]
23103        [EXCERPT]
23104        a1
23105        b1
23106        [EXCERPT]
23107        [FOLDED]
23108        [EXCERPT]
23109        [FOLDED]
23110        "
23111    });
23112    cx.simulate_keystroke("down");
23113    cx.assert_excerpts_with_selections(indoc! {"
23114        [EXCERPT]
23115        [FOLDED]
23116        [EXCERPT]
23117        ˇa1
23118        b1
23119        [EXCERPT]
23120        [FOLDED]
23121        [EXCERPT]
23122        [FOLDED]
23123        "
23124    });
23125    cx.simulate_keystroke("down");
23126    cx.assert_excerpts_with_selections(indoc! {"
23127        [EXCERPT]
23128        [FOLDED]
23129        [EXCERPT]
23130        a1
23131        ˇb1
23132        [EXCERPT]
23133        [FOLDED]
23134        [EXCERPT]
23135        [FOLDED]
23136        "
23137    });
23138    cx.simulate_keystroke("down");
23139    cx.assert_excerpts_with_selections(indoc! {"
23140        [EXCERPT]
23141        [FOLDED]
23142        [EXCERPT]
23143        a1
23144        b1
23145        ˇ[EXCERPT]
23146        [FOLDED]
23147        [EXCERPT]
23148        [FOLDED]
23149        "
23150    });
23151    cx.simulate_keystroke("down");
23152    cx.assert_excerpts_with_selections(indoc! {"
23153        [EXCERPT]
23154        [FOLDED]
23155        [EXCERPT]
23156        a1
23157        b1
23158        [EXCERPT]
23159        ˇ[FOLDED]
23160        [EXCERPT]
23161        [FOLDED]
23162        "
23163    });
23164    for _ in 0..5 {
23165        cx.simulate_keystroke("down");
23166        cx.assert_excerpts_with_selections(indoc! {"
23167            [EXCERPT]
23168            [FOLDED]
23169            [EXCERPT]
23170            a1
23171            b1
23172            [EXCERPT]
23173            [FOLDED]
23174            [EXCERPT]
23175            ˇ[FOLDED]
23176            "
23177        });
23178    }
23179
23180    cx.simulate_keystroke("up");
23181    cx.assert_excerpts_with_selections(indoc! {"
23182        [EXCERPT]
23183        [FOLDED]
23184        [EXCERPT]
23185        a1
23186        b1
23187        [EXCERPT]
23188        ˇ[FOLDED]
23189        [EXCERPT]
23190        [FOLDED]
23191        "
23192    });
23193    cx.simulate_keystroke("up");
23194    cx.assert_excerpts_with_selections(indoc! {"
23195        [EXCERPT]
23196        [FOLDED]
23197        [EXCERPT]
23198        a1
23199        b1
23200        ˇ[EXCERPT]
23201        [FOLDED]
23202        [EXCERPT]
23203        [FOLDED]
23204        "
23205    });
23206    cx.simulate_keystroke("up");
23207    cx.assert_excerpts_with_selections(indoc! {"
23208        [EXCERPT]
23209        [FOLDED]
23210        [EXCERPT]
23211        a1
23212        ˇb1
23213        [EXCERPT]
23214        [FOLDED]
23215        [EXCERPT]
23216        [FOLDED]
23217        "
23218    });
23219    cx.simulate_keystroke("up");
23220    cx.assert_excerpts_with_selections(indoc! {"
23221        [EXCERPT]
23222        [FOLDED]
23223        [EXCERPT]
23224        ˇa1
23225        b1
23226        [EXCERPT]
23227        [FOLDED]
23228        [EXCERPT]
23229        [FOLDED]
23230        "
23231    });
23232    for _ in 0..5 {
23233        cx.simulate_keystroke("up");
23234        cx.assert_excerpts_with_selections(indoc! {"
23235            [EXCERPT]
23236            ˇ[FOLDED]
23237            [EXCERPT]
23238            a1
23239            b1
23240            [EXCERPT]
23241            [FOLDED]
23242            [EXCERPT]
23243            [FOLDED]
23244            "
23245        });
23246    }
23247}
23248
23249#[gpui::test]
23250async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23251    init_test(cx, |_| {});
23252
23253    // Simple insertion
23254    assert_highlighted_edits(
23255        "Hello, world!",
23256        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23257        true,
23258        cx,
23259        |highlighted_edits, cx| {
23260            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23261            assert_eq!(highlighted_edits.highlights.len(), 1);
23262            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23263            assert_eq!(
23264                highlighted_edits.highlights[0].1.background_color,
23265                Some(cx.theme().status().created_background)
23266            );
23267        },
23268    )
23269    .await;
23270
23271    // Replacement
23272    assert_highlighted_edits(
23273        "This is a test.",
23274        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23275        false,
23276        cx,
23277        |highlighted_edits, cx| {
23278            assert_eq!(highlighted_edits.text, "That is a test.");
23279            assert_eq!(highlighted_edits.highlights.len(), 1);
23280            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23281            assert_eq!(
23282                highlighted_edits.highlights[0].1.background_color,
23283                Some(cx.theme().status().created_background)
23284            );
23285        },
23286    )
23287    .await;
23288
23289    // Multiple edits
23290    assert_highlighted_edits(
23291        "Hello, world!",
23292        vec![
23293            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23294            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23295        ],
23296        false,
23297        cx,
23298        |highlighted_edits, cx| {
23299            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23300            assert_eq!(highlighted_edits.highlights.len(), 2);
23301            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23302            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23303            assert_eq!(
23304                highlighted_edits.highlights[0].1.background_color,
23305                Some(cx.theme().status().created_background)
23306            );
23307            assert_eq!(
23308                highlighted_edits.highlights[1].1.background_color,
23309                Some(cx.theme().status().created_background)
23310            );
23311        },
23312    )
23313    .await;
23314
23315    // Multiple lines with edits
23316    assert_highlighted_edits(
23317        "First line\nSecond line\nThird line\nFourth line",
23318        vec![
23319            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23320            (
23321                Point::new(2, 0)..Point::new(2, 10),
23322                "New third line".to_string(),
23323            ),
23324            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23325        ],
23326        false,
23327        cx,
23328        |highlighted_edits, cx| {
23329            assert_eq!(
23330                highlighted_edits.text,
23331                "Second modified\nNew third line\nFourth updated line"
23332            );
23333            assert_eq!(highlighted_edits.highlights.len(), 3);
23334            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23335            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23336            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23337            for highlight in &highlighted_edits.highlights {
23338                assert_eq!(
23339                    highlight.1.background_color,
23340                    Some(cx.theme().status().created_background)
23341                );
23342            }
23343        },
23344    )
23345    .await;
23346}
23347
23348#[gpui::test]
23349async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23350    init_test(cx, |_| {});
23351
23352    // Deletion
23353    assert_highlighted_edits(
23354        "Hello, world!",
23355        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23356        true,
23357        cx,
23358        |highlighted_edits, cx| {
23359            assert_eq!(highlighted_edits.text, "Hello, world!");
23360            assert_eq!(highlighted_edits.highlights.len(), 1);
23361            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23362            assert_eq!(
23363                highlighted_edits.highlights[0].1.background_color,
23364                Some(cx.theme().status().deleted_background)
23365            );
23366        },
23367    )
23368    .await;
23369
23370    // Insertion
23371    assert_highlighted_edits(
23372        "Hello, world!",
23373        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23374        true,
23375        cx,
23376        |highlighted_edits, cx| {
23377            assert_eq!(highlighted_edits.highlights.len(), 1);
23378            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23379            assert_eq!(
23380                highlighted_edits.highlights[0].1.background_color,
23381                Some(cx.theme().status().created_background)
23382            );
23383        },
23384    )
23385    .await;
23386}
23387
23388async fn assert_highlighted_edits(
23389    text: &str,
23390    edits: Vec<(Range<Point>, String)>,
23391    include_deletions: bool,
23392    cx: &mut TestAppContext,
23393    assertion_fn: impl Fn(HighlightedText, &App),
23394) {
23395    let window = cx.add_window(|window, cx| {
23396        let buffer = MultiBuffer::build_simple(text, cx);
23397        Editor::new(EditorMode::full(), buffer, None, window, cx)
23398    });
23399    let cx = &mut VisualTestContext::from_window(*window, cx);
23400
23401    let (buffer, snapshot) = window
23402        .update(cx, |editor, _window, cx| {
23403            (
23404                editor.buffer().clone(),
23405                editor.buffer().read(cx).snapshot(cx),
23406            )
23407        })
23408        .unwrap();
23409
23410    let edits = edits
23411        .into_iter()
23412        .map(|(range, edit)| {
23413            (
23414                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23415                edit,
23416            )
23417        })
23418        .collect::<Vec<_>>();
23419
23420    let text_anchor_edits = edits
23421        .clone()
23422        .into_iter()
23423        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23424        .collect::<Vec<_>>();
23425
23426    let edit_preview = window
23427        .update(cx, |_, _window, cx| {
23428            buffer
23429                .read(cx)
23430                .as_singleton()
23431                .unwrap()
23432                .read(cx)
23433                .preview_edits(text_anchor_edits.into(), cx)
23434        })
23435        .unwrap()
23436        .await;
23437
23438    cx.update(|_window, cx| {
23439        let highlighted_edits = edit_prediction_edit_text(
23440            snapshot.as_singleton().unwrap().2,
23441            &edits,
23442            &edit_preview,
23443            include_deletions,
23444            cx,
23445        );
23446        assertion_fn(highlighted_edits, cx)
23447    });
23448}
23449
23450#[track_caller]
23451fn assert_breakpoint(
23452    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23453    path: &Arc<Path>,
23454    expected: Vec<(u32, Breakpoint)>,
23455) {
23456    if expected.is_empty() {
23457        assert!(!breakpoints.contains_key(path), "{}", path.display());
23458    } else {
23459        let mut breakpoint = breakpoints
23460            .get(path)
23461            .unwrap()
23462            .iter()
23463            .map(|breakpoint| {
23464                (
23465                    breakpoint.row,
23466                    Breakpoint {
23467                        message: breakpoint.message.clone(),
23468                        state: breakpoint.state,
23469                        condition: breakpoint.condition.clone(),
23470                        hit_condition: breakpoint.hit_condition.clone(),
23471                    },
23472                )
23473            })
23474            .collect::<Vec<_>>();
23475
23476        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23477
23478        assert_eq!(expected, breakpoint);
23479    }
23480}
23481
23482fn add_log_breakpoint_at_cursor(
23483    editor: &mut Editor,
23484    log_message: &str,
23485    window: &mut Window,
23486    cx: &mut Context<Editor>,
23487) {
23488    let (anchor, bp) = editor
23489        .breakpoints_at_cursors(window, cx)
23490        .first()
23491        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23492        .unwrap_or_else(|| {
23493            let snapshot = editor.snapshot(window, cx);
23494            let cursor_position: Point =
23495                editor.selections.newest(&snapshot.display_snapshot).head();
23496
23497            let breakpoint_position = snapshot
23498                .buffer_snapshot()
23499                .anchor_before(Point::new(cursor_position.row, 0));
23500
23501            (breakpoint_position, Breakpoint::new_log(log_message))
23502        });
23503
23504    editor.edit_breakpoint_at_anchor(
23505        anchor,
23506        bp,
23507        BreakpointEditAction::EditLogMessage(log_message.into()),
23508        cx,
23509    );
23510}
23511
23512#[gpui::test]
23513async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23514    init_test(cx, |_| {});
23515
23516    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23517    let fs = FakeFs::new(cx.executor());
23518    fs.insert_tree(
23519        path!("/a"),
23520        json!({
23521            "main.rs": sample_text,
23522        }),
23523    )
23524    .await;
23525    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23526    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23527    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23528
23529    let fs = FakeFs::new(cx.executor());
23530    fs.insert_tree(
23531        path!("/a"),
23532        json!({
23533            "main.rs": sample_text,
23534        }),
23535    )
23536    .await;
23537    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23538    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23539    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23540    let worktree_id = workspace
23541        .update(cx, |workspace, _window, cx| {
23542            workspace.project().update(cx, |project, cx| {
23543                project.worktrees(cx).next().unwrap().read(cx).id()
23544            })
23545        })
23546        .unwrap();
23547
23548    let buffer = project
23549        .update(cx, |project, cx| {
23550            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23551        })
23552        .await
23553        .unwrap();
23554
23555    let (editor, cx) = cx.add_window_view(|window, cx| {
23556        Editor::new(
23557            EditorMode::full(),
23558            MultiBuffer::build_from_buffer(buffer, cx),
23559            Some(project.clone()),
23560            window,
23561            cx,
23562        )
23563    });
23564
23565    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23566    let abs_path = project.read_with(cx, |project, cx| {
23567        project
23568            .absolute_path(&project_path, cx)
23569            .map(Arc::from)
23570            .unwrap()
23571    });
23572
23573    // assert we can add breakpoint on the first line
23574    editor.update_in(cx, |editor, window, cx| {
23575        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23576        editor.move_to_end(&MoveToEnd, window, cx);
23577        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23578    });
23579
23580    let breakpoints = editor.update(cx, |editor, cx| {
23581        editor
23582            .breakpoint_store()
23583            .as_ref()
23584            .unwrap()
23585            .read(cx)
23586            .all_source_breakpoints(cx)
23587    });
23588
23589    assert_eq!(1, breakpoints.len());
23590    assert_breakpoint(
23591        &breakpoints,
23592        &abs_path,
23593        vec![
23594            (0, Breakpoint::new_standard()),
23595            (3, Breakpoint::new_standard()),
23596        ],
23597    );
23598
23599    editor.update_in(cx, |editor, window, cx| {
23600        editor.move_to_beginning(&MoveToBeginning, window, cx);
23601        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23602    });
23603
23604    let breakpoints = editor.update(cx, |editor, cx| {
23605        editor
23606            .breakpoint_store()
23607            .as_ref()
23608            .unwrap()
23609            .read(cx)
23610            .all_source_breakpoints(cx)
23611    });
23612
23613    assert_eq!(1, breakpoints.len());
23614    assert_breakpoint(
23615        &breakpoints,
23616        &abs_path,
23617        vec![(3, Breakpoint::new_standard())],
23618    );
23619
23620    editor.update_in(cx, |editor, window, cx| {
23621        editor.move_to_end(&MoveToEnd, window, cx);
23622        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23623    });
23624
23625    let breakpoints = editor.update(cx, |editor, cx| {
23626        editor
23627            .breakpoint_store()
23628            .as_ref()
23629            .unwrap()
23630            .read(cx)
23631            .all_source_breakpoints(cx)
23632    });
23633
23634    assert_eq!(0, breakpoints.len());
23635    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23636}
23637
23638#[gpui::test]
23639async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23640    init_test(cx, |_| {});
23641
23642    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23643
23644    let fs = FakeFs::new(cx.executor());
23645    fs.insert_tree(
23646        path!("/a"),
23647        json!({
23648            "main.rs": sample_text,
23649        }),
23650    )
23651    .await;
23652    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23653    let (workspace, cx) =
23654        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23655
23656    let worktree_id = workspace.update(cx, |workspace, cx| {
23657        workspace.project().update(cx, |project, cx| {
23658            project.worktrees(cx).next().unwrap().read(cx).id()
23659        })
23660    });
23661
23662    let buffer = project
23663        .update(cx, |project, cx| {
23664            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23665        })
23666        .await
23667        .unwrap();
23668
23669    let (editor, cx) = cx.add_window_view(|window, cx| {
23670        Editor::new(
23671            EditorMode::full(),
23672            MultiBuffer::build_from_buffer(buffer, cx),
23673            Some(project.clone()),
23674            window,
23675            cx,
23676        )
23677    });
23678
23679    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23680    let abs_path = project.read_with(cx, |project, cx| {
23681        project
23682            .absolute_path(&project_path, cx)
23683            .map(Arc::from)
23684            .unwrap()
23685    });
23686
23687    editor.update_in(cx, |editor, window, cx| {
23688        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23689    });
23690
23691    let breakpoints = editor.update(cx, |editor, cx| {
23692        editor
23693            .breakpoint_store()
23694            .as_ref()
23695            .unwrap()
23696            .read(cx)
23697            .all_source_breakpoints(cx)
23698    });
23699
23700    assert_breakpoint(
23701        &breakpoints,
23702        &abs_path,
23703        vec![(0, Breakpoint::new_log("hello world"))],
23704    );
23705
23706    // Removing a log message from a log breakpoint should remove it
23707    editor.update_in(cx, |editor, window, cx| {
23708        add_log_breakpoint_at_cursor(editor, "", window, cx);
23709    });
23710
23711    let breakpoints = editor.update(cx, |editor, cx| {
23712        editor
23713            .breakpoint_store()
23714            .as_ref()
23715            .unwrap()
23716            .read(cx)
23717            .all_source_breakpoints(cx)
23718    });
23719
23720    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23721
23722    editor.update_in(cx, |editor, window, cx| {
23723        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23724        editor.move_to_end(&MoveToEnd, window, cx);
23725        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23726        // Not adding a log message to a standard breakpoint shouldn't remove it
23727        add_log_breakpoint_at_cursor(editor, "", window, cx);
23728    });
23729
23730    let breakpoints = editor.update(cx, |editor, cx| {
23731        editor
23732            .breakpoint_store()
23733            .as_ref()
23734            .unwrap()
23735            .read(cx)
23736            .all_source_breakpoints(cx)
23737    });
23738
23739    assert_breakpoint(
23740        &breakpoints,
23741        &abs_path,
23742        vec![
23743            (0, Breakpoint::new_standard()),
23744            (3, Breakpoint::new_standard()),
23745        ],
23746    );
23747
23748    editor.update_in(cx, |editor, window, cx| {
23749        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23750    });
23751
23752    let breakpoints = editor.update(cx, |editor, cx| {
23753        editor
23754            .breakpoint_store()
23755            .as_ref()
23756            .unwrap()
23757            .read(cx)
23758            .all_source_breakpoints(cx)
23759    });
23760
23761    assert_breakpoint(
23762        &breakpoints,
23763        &abs_path,
23764        vec![
23765            (0, Breakpoint::new_standard()),
23766            (3, Breakpoint::new_log("hello world")),
23767        ],
23768    );
23769
23770    editor.update_in(cx, |editor, window, cx| {
23771        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23772    });
23773
23774    let breakpoints = editor.update(cx, |editor, cx| {
23775        editor
23776            .breakpoint_store()
23777            .as_ref()
23778            .unwrap()
23779            .read(cx)
23780            .all_source_breakpoints(cx)
23781    });
23782
23783    assert_breakpoint(
23784        &breakpoints,
23785        &abs_path,
23786        vec![
23787            (0, Breakpoint::new_standard()),
23788            (3, Breakpoint::new_log("hello Earth!!")),
23789        ],
23790    );
23791}
23792
23793/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23794/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23795/// or when breakpoints were placed out of order. This tests for a regression too
23796#[gpui::test]
23797async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23798    init_test(cx, |_| {});
23799
23800    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23801    let fs = FakeFs::new(cx.executor());
23802    fs.insert_tree(
23803        path!("/a"),
23804        json!({
23805            "main.rs": sample_text,
23806        }),
23807    )
23808    .await;
23809    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23810    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23811    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23812
23813    let fs = FakeFs::new(cx.executor());
23814    fs.insert_tree(
23815        path!("/a"),
23816        json!({
23817            "main.rs": sample_text,
23818        }),
23819    )
23820    .await;
23821    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23822    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23823    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23824    let worktree_id = workspace
23825        .update(cx, |workspace, _window, cx| {
23826            workspace.project().update(cx, |project, cx| {
23827                project.worktrees(cx).next().unwrap().read(cx).id()
23828            })
23829        })
23830        .unwrap();
23831
23832    let buffer = project
23833        .update(cx, |project, cx| {
23834            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23835        })
23836        .await
23837        .unwrap();
23838
23839    let (editor, cx) = cx.add_window_view(|window, cx| {
23840        Editor::new(
23841            EditorMode::full(),
23842            MultiBuffer::build_from_buffer(buffer, cx),
23843            Some(project.clone()),
23844            window,
23845            cx,
23846        )
23847    });
23848
23849    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23850    let abs_path = project.read_with(cx, |project, cx| {
23851        project
23852            .absolute_path(&project_path, cx)
23853            .map(Arc::from)
23854            .unwrap()
23855    });
23856
23857    // assert we can add breakpoint on the first line
23858    editor.update_in(cx, |editor, window, cx| {
23859        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23860        editor.move_to_end(&MoveToEnd, window, cx);
23861        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23862        editor.move_up(&MoveUp, window, cx);
23863        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23864    });
23865
23866    let breakpoints = editor.update(cx, |editor, cx| {
23867        editor
23868            .breakpoint_store()
23869            .as_ref()
23870            .unwrap()
23871            .read(cx)
23872            .all_source_breakpoints(cx)
23873    });
23874
23875    assert_eq!(1, breakpoints.len());
23876    assert_breakpoint(
23877        &breakpoints,
23878        &abs_path,
23879        vec![
23880            (0, Breakpoint::new_standard()),
23881            (2, Breakpoint::new_standard()),
23882            (3, Breakpoint::new_standard()),
23883        ],
23884    );
23885
23886    editor.update_in(cx, |editor, window, cx| {
23887        editor.move_to_beginning(&MoveToBeginning, window, cx);
23888        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23889        editor.move_to_end(&MoveToEnd, window, cx);
23890        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23891        // Disabling a breakpoint that doesn't exist should do nothing
23892        editor.move_up(&MoveUp, window, cx);
23893        editor.move_up(&MoveUp, window, cx);
23894        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23895    });
23896
23897    let breakpoints = editor.update(cx, |editor, cx| {
23898        editor
23899            .breakpoint_store()
23900            .as_ref()
23901            .unwrap()
23902            .read(cx)
23903            .all_source_breakpoints(cx)
23904    });
23905
23906    let disable_breakpoint = {
23907        let mut bp = Breakpoint::new_standard();
23908        bp.state = BreakpointState::Disabled;
23909        bp
23910    };
23911
23912    assert_eq!(1, breakpoints.len());
23913    assert_breakpoint(
23914        &breakpoints,
23915        &abs_path,
23916        vec![
23917            (0, disable_breakpoint.clone()),
23918            (2, Breakpoint::new_standard()),
23919            (3, disable_breakpoint.clone()),
23920        ],
23921    );
23922
23923    editor.update_in(cx, |editor, window, cx| {
23924        editor.move_to_beginning(&MoveToBeginning, window, cx);
23925        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23926        editor.move_to_end(&MoveToEnd, window, cx);
23927        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23928        editor.move_up(&MoveUp, window, cx);
23929        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23930    });
23931
23932    let breakpoints = editor.update(cx, |editor, cx| {
23933        editor
23934            .breakpoint_store()
23935            .as_ref()
23936            .unwrap()
23937            .read(cx)
23938            .all_source_breakpoints(cx)
23939    });
23940
23941    assert_eq!(1, breakpoints.len());
23942    assert_breakpoint(
23943        &breakpoints,
23944        &abs_path,
23945        vec![
23946            (0, Breakpoint::new_standard()),
23947            (2, disable_breakpoint),
23948            (3, Breakpoint::new_standard()),
23949        ],
23950    );
23951}
23952
23953#[gpui::test]
23954async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23955    init_test(cx, |_| {});
23956    let capabilities = lsp::ServerCapabilities {
23957        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23958            prepare_provider: Some(true),
23959            work_done_progress_options: Default::default(),
23960        })),
23961        ..Default::default()
23962    };
23963    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23964
23965    cx.set_state(indoc! {"
23966        struct Fˇoo {}
23967    "});
23968
23969    cx.update_editor(|editor, _, cx| {
23970        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23971        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23972        editor.highlight_background::<DocumentHighlightRead>(
23973            &[highlight_range],
23974            |theme| theme.colors().editor_document_highlight_read_background,
23975            cx,
23976        );
23977    });
23978
23979    let mut prepare_rename_handler = cx
23980        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23981            move |_, _, _| async move {
23982                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23983                    start: lsp::Position {
23984                        line: 0,
23985                        character: 7,
23986                    },
23987                    end: lsp::Position {
23988                        line: 0,
23989                        character: 10,
23990                    },
23991                })))
23992            },
23993        );
23994    let prepare_rename_task = cx
23995        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23996        .expect("Prepare rename was not started");
23997    prepare_rename_handler.next().await.unwrap();
23998    prepare_rename_task.await.expect("Prepare rename failed");
23999
24000    let mut rename_handler =
24001        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24002            let edit = lsp::TextEdit {
24003                range: lsp::Range {
24004                    start: lsp::Position {
24005                        line: 0,
24006                        character: 7,
24007                    },
24008                    end: lsp::Position {
24009                        line: 0,
24010                        character: 10,
24011                    },
24012                },
24013                new_text: "FooRenamed".to_string(),
24014            };
24015            Ok(Some(lsp::WorkspaceEdit::new(
24016                // Specify the same edit twice
24017                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24018            )))
24019        });
24020    let rename_task = cx
24021        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24022        .expect("Confirm rename was not started");
24023    rename_handler.next().await.unwrap();
24024    rename_task.await.expect("Confirm rename failed");
24025    cx.run_until_parked();
24026
24027    // Despite two edits, only one is actually applied as those are identical
24028    cx.assert_editor_state(indoc! {"
24029        struct FooRenamedˇ {}
24030    "});
24031}
24032
24033#[gpui::test]
24034async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24035    init_test(cx, |_| {});
24036    // These capabilities indicate that the server does not support prepare rename.
24037    let capabilities = lsp::ServerCapabilities {
24038        rename_provider: Some(lsp::OneOf::Left(true)),
24039        ..Default::default()
24040    };
24041    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24042
24043    cx.set_state(indoc! {"
24044        struct Fˇoo {}
24045    "});
24046
24047    cx.update_editor(|editor, _window, cx| {
24048        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24049        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24050        editor.highlight_background::<DocumentHighlightRead>(
24051            &[highlight_range],
24052            |theme| theme.colors().editor_document_highlight_read_background,
24053            cx,
24054        );
24055    });
24056
24057    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24058        .expect("Prepare rename was not started")
24059        .await
24060        .expect("Prepare rename failed");
24061
24062    let mut rename_handler =
24063        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24064            let edit = lsp::TextEdit {
24065                range: lsp::Range {
24066                    start: lsp::Position {
24067                        line: 0,
24068                        character: 7,
24069                    },
24070                    end: lsp::Position {
24071                        line: 0,
24072                        character: 10,
24073                    },
24074                },
24075                new_text: "FooRenamed".to_string(),
24076            };
24077            Ok(Some(lsp::WorkspaceEdit::new(
24078                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24079            )))
24080        });
24081    let rename_task = cx
24082        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24083        .expect("Confirm rename was not started");
24084    rename_handler.next().await.unwrap();
24085    rename_task.await.expect("Confirm rename failed");
24086    cx.run_until_parked();
24087
24088    // Correct range is renamed, as `surrounding_word` is used to find it.
24089    cx.assert_editor_state(indoc! {"
24090        struct FooRenamedˇ {}
24091    "});
24092}
24093
24094#[gpui::test]
24095async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24096    init_test(cx, |_| {});
24097    let mut cx = EditorTestContext::new(cx).await;
24098
24099    let language = Arc::new(
24100        Language::new(
24101            LanguageConfig::default(),
24102            Some(tree_sitter_html::LANGUAGE.into()),
24103        )
24104        .with_brackets_query(
24105            r#"
24106            ("<" @open "/>" @close)
24107            ("</" @open ">" @close)
24108            ("<" @open ">" @close)
24109            ("\"" @open "\"" @close)
24110            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24111        "#,
24112        )
24113        .unwrap(),
24114    );
24115    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24116
24117    cx.set_state(indoc! {"
24118        <span>ˇ</span>
24119    "});
24120    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24121    cx.assert_editor_state(indoc! {"
24122        <span>
24123        ˇ
24124        </span>
24125    "});
24126
24127    cx.set_state(indoc! {"
24128        <span><span></span>ˇ</span>
24129    "});
24130    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24131    cx.assert_editor_state(indoc! {"
24132        <span><span></span>
24133        ˇ</span>
24134    "});
24135
24136    cx.set_state(indoc! {"
24137        <span>ˇ
24138        </span>
24139    "});
24140    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24141    cx.assert_editor_state(indoc! {"
24142        <span>
24143        ˇ
24144        </span>
24145    "});
24146}
24147
24148#[gpui::test(iterations = 10)]
24149async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
24150    init_test(cx, |_| {});
24151
24152    let fs = FakeFs::new(cx.executor());
24153    fs.insert_tree(
24154        path!("/dir"),
24155        json!({
24156            "a.ts": "a",
24157        }),
24158    )
24159    .await;
24160
24161    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
24162    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24163    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24164
24165    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24166    language_registry.add(Arc::new(Language::new(
24167        LanguageConfig {
24168            name: "TypeScript".into(),
24169            matcher: LanguageMatcher {
24170                path_suffixes: vec!["ts".to_string()],
24171                ..Default::default()
24172            },
24173            ..Default::default()
24174        },
24175        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24176    )));
24177    let mut fake_language_servers = language_registry.register_fake_lsp(
24178        "TypeScript",
24179        FakeLspAdapter {
24180            capabilities: lsp::ServerCapabilities {
24181                code_lens_provider: Some(lsp::CodeLensOptions {
24182                    resolve_provider: Some(true),
24183                }),
24184                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24185                    commands: vec!["_the/command".to_string()],
24186                    ..lsp::ExecuteCommandOptions::default()
24187                }),
24188                ..lsp::ServerCapabilities::default()
24189            },
24190            ..FakeLspAdapter::default()
24191        },
24192    );
24193
24194    let editor = workspace
24195        .update(cx, |workspace, window, cx| {
24196            workspace.open_abs_path(
24197                PathBuf::from(path!("/dir/a.ts")),
24198                OpenOptions::default(),
24199                window,
24200                cx,
24201            )
24202        })
24203        .unwrap()
24204        .await
24205        .unwrap()
24206        .downcast::<Editor>()
24207        .unwrap();
24208    cx.executor().run_until_parked();
24209
24210    let fake_server = fake_language_servers.next().await.unwrap();
24211
24212    let buffer = editor.update(cx, |editor, cx| {
24213        editor
24214            .buffer()
24215            .read(cx)
24216            .as_singleton()
24217            .expect("have opened a single file by path")
24218    });
24219
24220    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24221    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24222    drop(buffer_snapshot);
24223    let actions = cx
24224        .update_window(*workspace, |_, window, cx| {
24225            project.code_actions(&buffer, anchor..anchor, window, cx)
24226        })
24227        .unwrap();
24228
24229    fake_server
24230        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24231            Ok(Some(vec![
24232                lsp::CodeLens {
24233                    range: lsp::Range::default(),
24234                    command: Some(lsp::Command {
24235                        title: "Code lens command".to_owned(),
24236                        command: "_the/command".to_owned(),
24237                        arguments: None,
24238                    }),
24239                    data: None,
24240                },
24241                lsp::CodeLens {
24242                    range: lsp::Range::default(),
24243                    command: Some(lsp::Command {
24244                        title: "Command not in capabilities".to_owned(),
24245                        command: "not in capabilities".to_owned(),
24246                        arguments: None,
24247                    }),
24248                    data: None,
24249                },
24250                lsp::CodeLens {
24251                    range: lsp::Range {
24252                        start: lsp::Position {
24253                            line: 1,
24254                            character: 1,
24255                        },
24256                        end: lsp::Position {
24257                            line: 1,
24258                            character: 1,
24259                        },
24260                    },
24261                    command: Some(lsp::Command {
24262                        title: "Command not in range".to_owned(),
24263                        command: "_the/command".to_owned(),
24264                        arguments: None,
24265                    }),
24266                    data: None,
24267                },
24268            ]))
24269        })
24270        .next()
24271        .await;
24272
24273    let actions = actions.await.unwrap();
24274    assert_eq!(
24275        actions.len(),
24276        1,
24277        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24278    );
24279    let action = actions[0].clone();
24280    let apply = project.update(cx, |project, cx| {
24281        project.apply_code_action(buffer.clone(), action, true, cx)
24282    });
24283
24284    // Resolving the code action does not populate its edits. In absence of
24285    // edits, we must execute the given command.
24286    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24287        |mut lens, _| async move {
24288            let lens_command = lens.command.as_mut().expect("should have a command");
24289            assert_eq!(lens_command.title, "Code lens command");
24290            lens_command.arguments = Some(vec![json!("the-argument")]);
24291            Ok(lens)
24292        },
24293    );
24294
24295    // While executing the command, the language server sends the editor
24296    // a `workspaceEdit` request.
24297    fake_server
24298        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24299            let fake = fake_server.clone();
24300            move |params, _| {
24301                assert_eq!(params.command, "_the/command");
24302                let fake = fake.clone();
24303                async move {
24304                    fake.server
24305                        .request::<lsp::request::ApplyWorkspaceEdit>(
24306                            lsp::ApplyWorkspaceEditParams {
24307                                label: None,
24308                                edit: lsp::WorkspaceEdit {
24309                                    changes: Some(
24310                                        [(
24311                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24312                                            vec![lsp::TextEdit {
24313                                                range: lsp::Range::new(
24314                                                    lsp::Position::new(0, 0),
24315                                                    lsp::Position::new(0, 0),
24316                                                ),
24317                                                new_text: "X".into(),
24318                                            }],
24319                                        )]
24320                                        .into_iter()
24321                                        .collect(),
24322                                    ),
24323                                    ..lsp::WorkspaceEdit::default()
24324                                },
24325                            },
24326                        )
24327                        .await
24328                        .into_response()
24329                        .unwrap();
24330                    Ok(Some(json!(null)))
24331                }
24332            }
24333        })
24334        .next()
24335        .await;
24336
24337    // Applying the code lens command returns a project transaction containing the edits
24338    // sent by the language server in its `workspaceEdit` request.
24339    let transaction = apply.await.unwrap();
24340    assert!(transaction.0.contains_key(&buffer));
24341    buffer.update(cx, |buffer, cx| {
24342        assert_eq!(buffer.text(), "Xa");
24343        buffer.undo(cx);
24344        assert_eq!(buffer.text(), "a");
24345    });
24346
24347    let actions_after_edits = cx
24348        .update_window(*workspace, |_, window, cx| {
24349            project.code_actions(&buffer, anchor..anchor, window, cx)
24350        })
24351        .unwrap()
24352        .await
24353        .unwrap();
24354    assert_eq!(
24355        actions, actions_after_edits,
24356        "For the same selection, same code lens actions should be returned"
24357    );
24358
24359    let _responses =
24360        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24361            panic!("No more code lens requests are expected");
24362        });
24363    editor.update_in(cx, |editor, window, cx| {
24364        editor.select_all(&SelectAll, window, cx);
24365    });
24366    cx.executor().run_until_parked();
24367    let new_actions = cx
24368        .update_window(*workspace, |_, window, cx| {
24369            project.code_actions(&buffer, anchor..anchor, window, cx)
24370        })
24371        .unwrap()
24372        .await
24373        .unwrap();
24374    assert_eq!(
24375        actions, new_actions,
24376        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24377    );
24378}
24379
24380#[gpui::test]
24381async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24382    init_test(cx, |_| {});
24383
24384    let fs = FakeFs::new(cx.executor());
24385    let main_text = r#"fn main() {
24386println!("1");
24387println!("2");
24388println!("3");
24389println!("4");
24390println!("5");
24391}"#;
24392    let lib_text = "mod foo {}";
24393    fs.insert_tree(
24394        path!("/a"),
24395        json!({
24396            "lib.rs": lib_text,
24397            "main.rs": main_text,
24398        }),
24399    )
24400    .await;
24401
24402    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24403    let (workspace, cx) =
24404        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24405    let worktree_id = workspace.update(cx, |workspace, cx| {
24406        workspace.project().update(cx, |project, cx| {
24407            project.worktrees(cx).next().unwrap().read(cx).id()
24408        })
24409    });
24410
24411    let expected_ranges = vec![
24412        Point::new(0, 0)..Point::new(0, 0),
24413        Point::new(1, 0)..Point::new(1, 1),
24414        Point::new(2, 0)..Point::new(2, 2),
24415        Point::new(3, 0)..Point::new(3, 3),
24416    ];
24417
24418    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24419    let editor_1 = workspace
24420        .update_in(cx, |workspace, window, cx| {
24421            workspace.open_path(
24422                (worktree_id, rel_path("main.rs")),
24423                Some(pane_1.downgrade()),
24424                true,
24425                window,
24426                cx,
24427            )
24428        })
24429        .unwrap()
24430        .await
24431        .downcast::<Editor>()
24432        .unwrap();
24433    pane_1.update(cx, |pane, cx| {
24434        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24435        open_editor.update(cx, |editor, cx| {
24436            assert_eq!(
24437                editor.display_text(cx),
24438                main_text,
24439                "Original main.rs text on initial open",
24440            );
24441            assert_eq!(
24442                editor
24443                    .selections
24444                    .all::<Point>(&editor.display_snapshot(cx))
24445                    .into_iter()
24446                    .map(|s| s.range())
24447                    .collect::<Vec<_>>(),
24448                vec![Point::zero()..Point::zero()],
24449                "Default selections on initial open",
24450            );
24451        })
24452    });
24453    editor_1.update_in(cx, |editor, window, cx| {
24454        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24455            s.select_ranges(expected_ranges.clone());
24456        });
24457    });
24458
24459    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24460        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24461    });
24462    let editor_2 = workspace
24463        .update_in(cx, |workspace, window, cx| {
24464            workspace.open_path(
24465                (worktree_id, rel_path("main.rs")),
24466                Some(pane_2.downgrade()),
24467                true,
24468                window,
24469                cx,
24470            )
24471        })
24472        .unwrap()
24473        .await
24474        .downcast::<Editor>()
24475        .unwrap();
24476    pane_2.update(cx, |pane, cx| {
24477        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24478        open_editor.update(cx, |editor, cx| {
24479            assert_eq!(
24480                editor.display_text(cx),
24481                main_text,
24482                "Original main.rs text on initial open in another panel",
24483            );
24484            assert_eq!(
24485                editor
24486                    .selections
24487                    .all::<Point>(&editor.display_snapshot(cx))
24488                    .into_iter()
24489                    .map(|s| s.range())
24490                    .collect::<Vec<_>>(),
24491                vec![Point::zero()..Point::zero()],
24492                "Default selections on initial open in another panel",
24493            );
24494        })
24495    });
24496
24497    editor_2.update_in(cx, |editor, window, cx| {
24498        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24499    });
24500
24501    let _other_editor_1 = workspace
24502        .update_in(cx, |workspace, window, cx| {
24503            workspace.open_path(
24504                (worktree_id, rel_path("lib.rs")),
24505                Some(pane_1.downgrade()),
24506                true,
24507                window,
24508                cx,
24509            )
24510        })
24511        .unwrap()
24512        .await
24513        .downcast::<Editor>()
24514        .unwrap();
24515    pane_1
24516        .update_in(cx, |pane, window, cx| {
24517            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24518        })
24519        .await
24520        .unwrap();
24521    drop(editor_1);
24522    pane_1.update(cx, |pane, cx| {
24523        pane.active_item()
24524            .unwrap()
24525            .downcast::<Editor>()
24526            .unwrap()
24527            .update(cx, |editor, cx| {
24528                assert_eq!(
24529                    editor.display_text(cx),
24530                    lib_text,
24531                    "Other file should be open and active",
24532                );
24533            });
24534        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24535    });
24536
24537    let _other_editor_2 = workspace
24538        .update_in(cx, |workspace, window, cx| {
24539            workspace.open_path(
24540                (worktree_id, rel_path("lib.rs")),
24541                Some(pane_2.downgrade()),
24542                true,
24543                window,
24544                cx,
24545            )
24546        })
24547        .unwrap()
24548        .await
24549        .downcast::<Editor>()
24550        .unwrap();
24551    pane_2
24552        .update_in(cx, |pane, window, cx| {
24553            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24554        })
24555        .await
24556        .unwrap();
24557    drop(editor_2);
24558    pane_2.update(cx, |pane, cx| {
24559        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24560        open_editor.update(cx, |editor, cx| {
24561            assert_eq!(
24562                editor.display_text(cx),
24563                lib_text,
24564                "Other file should be open and active in another panel too",
24565            );
24566        });
24567        assert_eq!(
24568            pane.items().count(),
24569            1,
24570            "No other editors should be open in another pane",
24571        );
24572    });
24573
24574    let _editor_1_reopened = workspace
24575        .update_in(cx, |workspace, window, cx| {
24576            workspace.open_path(
24577                (worktree_id, rel_path("main.rs")),
24578                Some(pane_1.downgrade()),
24579                true,
24580                window,
24581                cx,
24582            )
24583        })
24584        .unwrap()
24585        .await
24586        .downcast::<Editor>()
24587        .unwrap();
24588    let _editor_2_reopened = workspace
24589        .update_in(cx, |workspace, window, cx| {
24590            workspace.open_path(
24591                (worktree_id, rel_path("main.rs")),
24592                Some(pane_2.downgrade()),
24593                true,
24594                window,
24595                cx,
24596            )
24597        })
24598        .unwrap()
24599        .await
24600        .downcast::<Editor>()
24601        .unwrap();
24602    pane_1.update(cx, |pane, cx| {
24603        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24604        open_editor.update(cx, |editor, cx| {
24605            assert_eq!(
24606                editor.display_text(cx),
24607                main_text,
24608                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24609            );
24610            assert_eq!(
24611                editor
24612                    .selections
24613                    .all::<Point>(&editor.display_snapshot(cx))
24614                    .into_iter()
24615                    .map(|s| s.range())
24616                    .collect::<Vec<_>>(),
24617                expected_ranges,
24618                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24619            );
24620        })
24621    });
24622    pane_2.update(cx, |pane, cx| {
24623        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24624        open_editor.update(cx, |editor, cx| {
24625            assert_eq!(
24626                editor.display_text(cx),
24627                r#"fn main() {
24628⋯rintln!("1");
24629⋯intln!("2");
24630⋯ntln!("3");
24631println!("4");
24632println!("5");
24633}"#,
24634                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24635            );
24636            assert_eq!(
24637                editor
24638                    .selections
24639                    .all::<Point>(&editor.display_snapshot(cx))
24640                    .into_iter()
24641                    .map(|s| s.range())
24642                    .collect::<Vec<_>>(),
24643                vec![Point::zero()..Point::zero()],
24644                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24645            );
24646        })
24647    });
24648}
24649
24650#[gpui::test]
24651async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24652    init_test(cx, |_| {});
24653
24654    let fs = FakeFs::new(cx.executor());
24655    let main_text = r#"fn main() {
24656println!("1");
24657println!("2");
24658println!("3");
24659println!("4");
24660println!("5");
24661}"#;
24662    let lib_text = "mod foo {}";
24663    fs.insert_tree(
24664        path!("/a"),
24665        json!({
24666            "lib.rs": lib_text,
24667            "main.rs": main_text,
24668        }),
24669    )
24670    .await;
24671
24672    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24673    let (workspace, cx) =
24674        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24675    let worktree_id = workspace.update(cx, |workspace, cx| {
24676        workspace.project().update(cx, |project, cx| {
24677            project.worktrees(cx).next().unwrap().read(cx).id()
24678        })
24679    });
24680
24681    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24682    let editor = workspace
24683        .update_in(cx, |workspace, window, cx| {
24684            workspace.open_path(
24685                (worktree_id, rel_path("main.rs")),
24686                Some(pane.downgrade()),
24687                true,
24688                window,
24689                cx,
24690            )
24691        })
24692        .unwrap()
24693        .await
24694        .downcast::<Editor>()
24695        .unwrap();
24696    pane.update(cx, |pane, cx| {
24697        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24698        open_editor.update(cx, |editor, cx| {
24699            assert_eq!(
24700                editor.display_text(cx),
24701                main_text,
24702                "Original main.rs text on initial open",
24703            );
24704        })
24705    });
24706    editor.update_in(cx, |editor, window, cx| {
24707        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24708    });
24709
24710    cx.update_global(|store: &mut SettingsStore, cx| {
24711        store.update_user_settings(cx, |s| {
24712            s.workspace.restore_on_file_reopen = Some(false);
24713        });
24714    });
24715    editor.update_in(cx, |editor, window, cx| {
24716        editor.fold_ranges(
24717            vec![
24718                Point::new(1, 0)..Point::new(1, 1),
24719                Point::new(2, 0)..Point::new(2, 2),
24720                Point::new(3, 0)..Point::new(3, 3),
24721            ],
24722            false,
24723            window,
24724            cx,
24725        );
24726    });
24727    pane.update_in(cx, |pane, window, cx| {
24728        pane.close_all_items(&CloseAllItems::default(), window, cx)
24729    })
24730    .await
24731    .unwrap();
24732    pane.update(cx, |pane, _| {
24733        assert!(pane.active_item().is_none());
24734    });
24735    cx.update_global(|store: &mut SettingsStore, cx| {
24736        store.update_user_settings(cx, |s| {
24737            s.workspace.restore_on_file_reopen = Some(true);
24738        });
24739    });
24740
24741    let _editor_reopened = workspace
24742        .update_in(cx, |workspace, window, cx| {
24743            workspace.open_path(
24744                (worktree_id, rel_path("main.rs")),
24745                Some(pane.downgrade()),
24746                true,
24747                window,
24748                cx,
24749            )
24750        })
24751        .unwrap()
24752        .await
24753        .downcast::<Editor>()
24754        .unwrap();
24755    pane.update(cx, |pane, cx| {
24756        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24757        open_editor.update(cx, |editor, cx| {
24758            assert_eq!(
24759                editor.display_text(cx),
24760                main_text,
24761                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24762            );
24763        })
24764    });
24765}
24766
24767#[gpui::test]
24768async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24769    struct EmptyModalView {
24770        focus_handle: gpui::FocusHandle,
24771    }
24772    impl EventEmitter<DismissEvent> for EmptyModalView {}
24773    impl Render for EmptyModalView {
24774        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24775            div()
24776        }
24777    }
24778    impl Focusable for EmptyModalView {
24779        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24780            self.focus_handle.clone()
24781        }
24782    }
24783    impl workspace::ModalView for EmptyModalView {}
24784    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24785        EmptyModalView {
24786            focus_handle: cx.focus_handle(),
24787        }
24788    }
24789
24790    init_test(cx, |_| {});
24791
24792    let fs = FakeFs::new(cx.executor());
24793    let project = Project::test(fs, [], cx).await;
24794    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24795    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24796    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24797    let editor = cx.new_window_entity(|window, cx| {
24798        Editor::new(
24799            EditorMode::full(),
24800            buffer,
24801            Some(project.clone()),
24802            window,
24803            cx,
24804        )
24805    });
24806    workspace
24807        .update(cx, |workspace, window, cx| {
24808            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24809        })
24810        .unwrap();
24811    editor.update_in(cx, |editor, window, cx| {
24812        editor.open_context_menu(&OpenContextMenu, window, cx);
24813        assert!(editor.mouse_context_menu.is_some());
24814    });
24815    workspace
24816        .update(cx, |workspace, window, cx| {
24817            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24818        })
24819        .unwrap();
24820    cx.read(|cx| {
24821        assert!(editor.read(cx).mouse_context_menu.is_none());
24822    });
24823}
24824
24825fn set_linked_edit_ranges(
24826    opening: (Point, Point),
24827    closing: (Point, Point),
24828    editor: &mut Editor,
24829    cx: &mut Context<Editor>,
24830) {
24831    let Some((buffer, _)) = editor
24832        .buffer
24833        .read(cx)
24834        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24835    else {
24836        panic!("Failed to get buffer for selection position");
24837    };
24838    let buffer = buffer.read(cx);
24839    let buffer_id = buffer.remote_id();
24840    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24841    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24842    let mut linked_ranges = HashMap::default();
24843    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24844    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24845}
24846
24847#[gpui::test]
24848async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24849    init_test(cx, |_| {});
24850
24851    let fs = FakeFs::new(cx.executor());
24852    fs.insert_file(path!("/file.html"), Default::default())
24853        .await;
24854
24855    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24856
24857    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24858    let html_language = Arc::new(Language::new(
24859        LanguageConfig {
24860            name: "HTML".into(),
24861            matcher: LanguageMatcher {
24862                path_suffixes: vec!["html".to_string()],
24863                ..LanguageMatcher::default()
24864            },
24865            brackets: BracketPairConfig {
24866                pairs: vec![BracketPair {
24867                    start: "<".into(),
24868                    end: ">".into(),
24869                    close: true,
24870                    ..Default::default()
24871                }],
24872                ..Default::default()
24873            },
24874            ..Default::default()
24875        },
24876        Some(tree_sitter_html::LANGUAGE.into()),
24877    ));
24878    language_registry.add(html_language);
24879    let mut fake_servers = language_registry.register_fake_lsp(
24880        "HTML",
24881        FakeLspAdapter {
24882            capabilities: lsp::ServerCapabilities {
24883                completion_provider: Some(lsp::CompletionOptions {
24884                    resolve_provider: Some(true),
24885                    ..Default::default()
24886                }),
24887                ..Default::default()
24888            },
24889            ..Default::default()
24890        },
24891    );
24892
24893    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24894    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24895
24896    let worktree_id = workspace
24897        .update(cx, |workspace, _window, cx| {
24898            workspace.project().update(cx, |project, cx| {
24899                project.worktrees(cx).next().unwrap().read(cx).id()
24900            })
24901        })
24902        .unwrap();
24903    project
24904        .update(cx, |project, cx| {
24905            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24906        })
24907        .await
24908        .unwrap();
24909    let editor = workspace
24910        .update(cx, |workspace, window, cx| {
24911            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24912        })
24913        .unwrap()
24914        .await
24915        .unwrap()
24916        .downcast::<Editor>()
24917        .unwrap();
24918
24919    let fake_server = fake_servers.next().await.unwrap();
24920    editor.update_in(cx, |editor, window, cx| {
24921        editor.set_text("<ad></ad>", window, cx);
24922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24923            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24924        });
24925        set_linked_edit_ranges(
24926            (Point::new(0, 1), Point::new(0, 3)),
24927            (Point::new(0, 6), Point::new(0, 8)),
24928            editor,
24929            cx,
24930        );
24931    });
24932    let mut completion_handle =
24933        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24934            Ok(Some(lsp::CompletionResponse::Array(vec![
24935                lsp::CompletionItem {
24936                    label: "head".to_string(),
24937                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24938                        lsp::InsertReplaceEdit {
24939                            new_text: "head".to_string(),
24940                            insert: lsp::Range::new(
24941                                lsp::Position::new(0, 1),
24942                                lsp::Position::new(0, 3),
24943                            ),
24944                            replace: lsp::Range::new(
24945                                lsp::Position::new(0, 1),
24946                                lsp::Position::new(0, 3),
24947                            ),
24948                        },
24949                    )),
24950                    ..Default::default()
24951                },
24952            ])))
24953        });
24954    editor.update_in(cx, |editor, window, cx| {
24955        editor.show_completions(&ShowCompletions, window, cx);
24956    });
24957    cx.run_until_parked();
24958    completion_handle.next().await.unwrap();
24959    editor.update(cx, |editor, _| {
24960        assert!(
24961            editor.context_menu_visible(),
24962            "Completion menu should be visible"
24963        );
24964    });
24965    editor.update_in(cx, |editor, window, cx| {
24966        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24967    });
24968    cx.executor().run_until_parked();
24969    editor.update(cx, |editor, cx| {
24970        assert_eq!(editor.text(cx), "<head></head>");
24971    });
24972}
24973
24974#[gpui::test]
24975async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24976    init_test(cx, |_| {});
24977
24978    let mut cx = EditorTestContext::new(cx).await;
24979    let language = Arc::new(Language::new(
24980        LanguageConfig {
24981            name: "TSX".into(),
24982            matcher: LanguageMatcher {
24983                path_suffixes: vec!["tsx".to_string()],
24984                ..LanguageMatcher::default()
24985            },
24986            brackets: BracketPairConfig {
24987                pairs: vec![BracketPair {
24988                    start: "<".into(),
24989                    end: ">".into(),
24990                    close: true,
24991                    ..Default::default()
24992                }],
24993                ..Default::default()
24994            },
24995            linked_edit_characters: HashSet::from_iter(['.']),
24996            ..Default::default()
24997        },
24998        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24999    ));
25000    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25001
25002    // Test typing > does not extend linked pair
25003    cx.set_state("<divˇ<div></div>");
25004    cx.update_editor(|editor, _, cx| {
25005        set_linked_edit_ranges(
25006            (Point::new(0, 1), Point::new(0, 4)),
25007            (Point::new(0, 11), Point::new(0, 14)),
25008            editor,
25009            cx,
25010        );
25011    });
25012    cx.update_editor(|editor, window, cx| {
25013        editor.handle_input(">", window, cx);
25014    });
25015    cx.assert_editor_state("<div>ˇ<div></div>");
25016
25017    // Test typing . do extend linked pair
25018    cx.set_state("<Animatedˇ></Animated>");
25019    cx.update_editor(|editor, _, cx| {
25020        set_linked_edit_ranges(
25021            (Point::new(0, 1), Point::new(0, 9)),
25022            (Point::new(0, 12), Point::new(0, 20)),
25023            editor,
25024            cx,
25025        );
25026    });
25027    cx.update_editor(|editor, window, cx| {
25028        editor.handle_input(".", window, cx);
25029    });
25030    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25031    cx.update_editor(|editor, _, cx| {
25032        set_linked_edit_ranges(
25033            (Point::new(0, 1), Point::new(0, 10)),
25034            (Point::new(0, 13), Point::new(0, 21)),
25035            editor,
25036            cx,
25037        );
25038    });
25039    cx.update_editor(|editor, window, cx| {
25040        editor.handle_input("V", window, cx);
25041    });
25042    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25043}
25044
25045#[gpui::test]
25046async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25047    init_test(cx, |_| {});
25048
25049    let fs = FakeFs::new(cx.executor());
25050    fs.insert_tree(
25051        path!("/root"),
25052        json!({
25053            "a": {
25054                "main.rs": "fn main() {}",
25055            },
25056            "foo": {
25057                "bar": {
25058                    "external_file.rs": "pub mod external {}",
25059                }
25060            }
25061        }),
25062    )
25063    .await;
25064
25065    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25066    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25067    language_registry.add(rust_lang());
25068    let _fake_servers = language_registry.register_fake_lsp(
25069        "Rust",
25070        FakeLspAdapter {
25071            ..FakeLspAdapter::default()
25072        },
25073    );
25074    let (workspace, cx) =
25075        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25076    let worktree_id = workspace.update(cx, |workspace, cx| {
25077        workspace.project().update(cx, |project, cx| {
25078            project.worktrees(cx).next().unwrap().read(cx).id()
25079        })
25080    });
25081
25082    let assert_language_servers_count =
25083        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25084            project.update(cx, |project, cx| {
25085                let current = project
25086                    .lsp_store()
25087                    .read(cx)
25088                    .as_local()
25089                    .unwrap()
25090                    .language_servers
25091                    .len();
25092                assert_eq!(expected, current, "{context}");
25093            });
25094        };
25095
25096    assert_language_servers_count(
25097        0,
25098        "No servers should be running before any file is open",
25099        cx,
25100    );
25101    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25102    let main_editor = workspace
25103        .update_in(cx, |workspace, window, cx| {
25104            workspace.open_path(
25105                (worktree_id, rel_path("main.rs")),
25106                Some(pane.downgrade()),
25107                true,
25108                window,
25109                cx,
25110            )
25111        })
25112        .unwrap()
25113        .await
25114        .downcast::<Editor>()
25115        .unwrap();
25116    pane.update(cx, |pane, cx| {
25117        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25118        open_editor.update(cx, |editor, cx| {
25119            assert_eq!(
25120                editor.display_text(cx),
25121                "fn main() {}",
25122                "Original main.rs text on initial open",
25123            );
25124        });
25125        assert_eq!(open_editor, main_editor);
25126    });
25127    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
25128
25129    let external_editor = workspace
25130        .update_in(cx, |workspace, window, cx| {
25131            workspace.open_abs_path(
25132                PathBuf::from("/root/foo/bar/external_file.rs"),
25133                OpenOptions::default(),
25134                window,
25135                cx,
25136            )
25137        })
25138        .await
25139        .expect("opening external file")
25140        .downcast::<Editor>()
25141        .expect("downcasted external file's open element to editor");
25142    pane.update(cx, |pane, cx| {
25143        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25144        open_editor.update(cx, |editor, cx| {
25145            assert_eq!(
25146                editor.display_text(cx),
25147                "pub mod external {}",
25148                "External file is open now",
25149            );
25150        });
25151        assert_eq!(open_editor, external_editor);
25152    });
25153    assert_language_servers_count(
25154        1,
25155        "Second, external, *.rs file should join the existing server",
25156        cx,
25157    );
25158
25159    pane.update_in(cx, |pane, window, cx| {
25160        pane.close_active_item(&CloseActiveItem::default(), window, cx)
25161    })
25162    .await
25163    .unwrap();
25164    pane.update_in(cx, |pane, window, cx| {
25165        pane.navigate_backward(&Default::default(), window, cx);
25166    });
25167    cx.run_until_parked();
25168    pane.update(cx, |pane, cx| {
25169        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25170        open_editor.update(cx, |editor, cx| {
25171            assert_eq!(
25172                editor.display_text(cx),
25173                "pub mod external {}",
25174                "External file is open now",
25175            );
25176        });
25177    });
25178    assert_language_servers_count(
25179        1,
25180        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25181        cx,
25182    );
25183
25184    cx.update(|_, cx| {
25185        workspace::reload(cx);
25186    });
25187    assert_language_servers_count(
25188        1,
25189        "After reloading the worktree with local and external files opened, only one project should be started",
25190        cx,
25191    );
25192}
25193
25194#[gpui::test]
25195async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25196    init_test(cx, |_| {});
25197
25198    let mut cx = EditorTestContext::new(cx).await;
25199    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25200    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25201
25202    // test cursor move to start of each line on tab
25203    // for `if`, `elif`, `else`, `while`, `with` and `for`
25204    cx.set_state(indoc! {"
25205        def main():
25206        ˇ    for item in items:
25207        ˇ        while item.active:
25208        ˇ            if item.value > 10:
25209        ˇ                continue
25210        ˇ            elif item.value < 0:
25211        ˇ                break
25212        ˇ            else:
25213        ˇ                with item.context() as ctx:
25214        ˇ                    yield count
25215        ˇ        else:
25216        ˇ            log('while else')
25217        ˇ    else:
25218        ˇ        log('for else')
25219    "});
25220    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25221    cx.assert_editor_state(indoc! {"
25222        def main():
25223            ˇfor item in items:
25224                ˇwhile item.active:
25225                    ˇif item.value > 10:
25226                        ˇcontinue
25227                    ˇelif item.value < 0:
25228                        ˇbreak
25229                    ˇelse:
25230                        ˇwith item.context() as ctx:
25231                            ˇyield count
25232                ˇelse:
25233                    ˇlog('while else')
25234            ˇelse:
25235                ˇlog('for else')
25236    "});
25237    // test relative indent is preserved when tab
25238    // for `if`, `elif`, `else`, `while`, `with` and `for`
25239    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25240    cx.assert_editor_state(indoc! {"
25241        def main():
25242                ˇfor item in items:
25243                    ˇwhile item.active:
25244                        ˇif item.value > 10:
25245                            ˇcontinue
25246                        ˇelif item.value < 0:
25247                            ˇbreak
25248                        ˇelse:
25249                            ˇwith item.context() as ctx:
25250                                ˇyield count
25251                    ˇelse:
25252                        ˇlog('while else')
25253                ˇelse:
25254                    ˇlog('for else')
25255    "});
25256
25257    // test cursor move to start of each line on tab
25258    // for `try`, `except`, `else`, `finally`, `match` and `def`
25259    cx.set_state(indoc! {"
25260        def main():
25261        ˇ    try:
25262        ˇ        fetch()
25263        ˇ    except ValueError:
25264        ˇ        handle_error()
25265        ˇ    else:
25266        ˇ        match value:
25267        ˇ            case _:
25268        ˇ    finally:
25269        ˇ        def status():
25270        ˇ            return 0
25271    "});
25272    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25273    cx.assert_editor_state(indoc! {"
25274        def main():
25275            ˇtry:
25276                ˇfetch()
25277            ˇexcept ValueError:
25278                ˇhandle_error()
25279            ˇelse:
25280                ˇmatch value:
25281                    ˇcase _:
25282            ˇfinally:
25283                ˇdef status():
25284                    ˇreturn 0
25285    "});
25286    // test relative indent is preserved when tab
25287    // for `try`, `except`, `else`, `finally`, `match` and `def`
25288    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25289    cx.assert_editor_state(indoc! {"
25290        def main():
25291                ˇtry:
25292                    ˇfetch()
25293                ˇexcept ValueError:
25294                    ˇhandle_error()
25295                ˇelse:
25296                    ˇmatch value:
25297                        ˇcase _:
25298                ˇfinally:
25299                    ˇdef status():
25300                        ˇreturn 0
25301    "});
25302}
25303
25304#[gpui::test]
25305async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25306    init_test(cx, |_| {});
25307
25308    let mut cx = EditorTestContext::new(cx).await;
25309    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25310    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25311
25312    // test `else` auto outdents when typed inside `if` block
25313    cx.set_state(indoc! {"
25314        def main():
25315            if i == 2:
25316                return
25317                ˇ
25318    "});
25319    cx.update_editor(|editor, window, cx| {
25320        editor.handle_input("else:", window, cx);
25321    });
25322    cx.assert_editor_state(indoc! {"
25323        def main():
25324            if i == 2:
25325                return
25326            else:ˇ
25327    "});
25328
25329    // test `except` auto outdents when typed inside `try` block
25330    cx.set_state(indoc! {"
25331        def main():
25332            try:
25333                i = 2
25334                ˇ
25335    "});
25336    cx.update_editor(|editor, window, cx| {
25337        editor.handle_input("except:", window, cx);
25338    });
25339    cx.assert_editor_state(indoc! {"
25340        def main():
25341            try:
25342                i = 2
25343            except:ˇ
25344    "});
25345
25346    // test `else` auto outdents when typed inside `except` block
25347    cx.set_state(indoc! {"
25348        def main():
25349            try:
25350                i = 2
25351            except:
25352                j = 2
25353                ˇ
25354    "});
25355    cx.update_editor(|editor, window, cx| {
25356        editor.handle_input("else:", window, cx);
25357    });
25358    cx.assert_editor_state(indoc! {"
25359        def main():
25360            try:
25361                i = 2
25362            except:
25363                j = 2
25364            else:ˇ
25365    "});
25366
25367    // test `finally` auto outdents when typed inside `else` block
25368    cx.set_state(indoc! {"
25369        def main():
25370            try:
25371                i = 2
25372            except:
25373                j = 2
25374            else:
25375                k = 2
25376                ˇ
25377    "});
25378    cx.update_editor(|editor, window, cx| {
25379        editor.handle_input("finally:", window, cx);
25380    });
25381    cx.assert_editor_state(indoc! {"
25382        def main():
25383            try:
25384                i = 2
25385            except:
25386                j = 2
25387            else:
25388                k = 2
25389            finally:ˇ
25390    "});
25391
25392    // test `else` does not outdents when typed inside `except` block right after for block
25393    cx.set_state(indoc! {"
25394        def main():
25395            try:
25396                i = 2
25397            except:
25398                for i in range(n):
25399                    pass
25400                ˇ
25401    "});
25402    cx.update_editor(|editor, window, cx| {
25403        editor.handle_input("else:", window, cx);
25404    });
25405    cx.assert_editor_state(indoc! {"
25406        def main():
25407            try:
25408                i = 2
25409            except:
25410                for i in range(n):
25411                    pass
25412                else:ˇ
25413    "});
25414
25415    // test `finally` auto outdents when typed inside `else` block right after for block
25416    cx.set_state(indoc! {"
25417        def main():
25418            try:
25419                i = 2
25420            except:
25421                j = 2
25422            else:
25423                for i in range(n):
25424                    pass
25425                ˇ
25426    "});
25427    cx.update_editor(|editor, window, cx| {
25428        editor.handle_input("finally:", window, cx);
25429    });
25430    cx.assert_editor_state(indoc! {"
25431        def main():
25432            try:
25433                i = 2
25434            except:
25435                j = 2
25436            else:
25437                for i in range(n):
25438                    pass
25439            finally:ˇ
25440    "});
25441
25442    // test `except` outdents to inner "try" block
25443    cx.set_state(indoc! {"
25444        def main():
25445            try:
25446                i = 2
25447                if i == 2:
25448                    try:
25449                        i = 3
25450                        ˇ
25451    "});
25452    cx.update_editor(|editor, window, cx| {
25453        editor.handle_input("except:", window, cx);
25454    });
25455    cx.assert_editor_state(indoc! {"
25456        def main():
25457            try:
25458                i = 2
25459                if i == 2:
25460                    try:
25461                        i = 3
25462                    except:ˇ
25463    "});
25464
25465    // test `except` outdents to outer "try" block
25466    cx.set_state(indoc! {"
25467        def main():
25468            try:
25469                i = 2
25470                if i == 2:
25471                    try:
25472                        i = 3
25473                ˇ
25474    "});
25475    cx.update_editor(|editor, window, cx| {
25476        editor.handle_input("except:", window, cx);
25477    });
25478    cx.assert_editor_state(indoc! {"
25479        def main():
25480            try:
25481                i = 2
25482                if i == 2:
25483                    try:
25484                        i = 3
25485            except:ˇ
25486    "});
25487
25488    // test `else` stays at correct indent when typed after `for` block
25489    cx.set_state(indoc! {"
25490        def main():
25491            for i in range(10):
25492                if i == 3:
25493                    break
25494            ˇ
25495    "});
25496    cx.update_editor(|editor, window, cx| {
25497        editor.handle_input("else:", window, cx);
25498    });
25499    cx.assert_editor_state(indoc! {"
25500        def main():
25501            for i in range(10):
25502                if i == 3:
25503                    break
25504            else:ˇ
25505    "});
25506
25507    // test does not outdent on typing after line with square brackets
25508    cx.set_state(indoc! {"
25509        def f() -> list[str]:
25510            ˇ
25511    "});
25512    cx.update_editor(|editor, window, cx| {
25513        editor.handle_input("a", window, cx);
25514    });
25515    cx.assert_editor_state(indoc! {"
25516        def f() -> list[str]:
2551725518    "});
25519
25520    // test does not outdent on typing : after case keyword
25521    cx.set_state(indoc! {"
25522        match 1:
25523            caseˇ
25524    "});
25525    cx.update_editor(|editor, window, cx| {
25526        editor.handle_input(":", window, cx);
25527    });
25528    cx.assert_editor_state(indoc! {"
25529        match 1:
25530            case:ˇ
25531    "});
25532}
25533
25534#[gpui::test]
25535async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25536    init_test(cx, |_| {});
25537    update_test_language_settings(cx, |settings| {
25538        settings.defaults.extend_comment_on_newline = Some(false);
25539    });
25540    let mut cx = EditorTestContext::new(cx).await;
25541    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25542    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25543
25544    // test correct indent after newline on comment
25545    cx.set_state(indoc! {"
25546        # COMMENT:ˇ
25547    "});
25548    cx.update_editor(|editor, window, cx| {
25549        editor.newline(&Newline, window, cx);
25550    });
25551    cx.assert_editor_state(indoc! {"
25552        # COMMENT:
25553        ˇ
25554    "});
25555
25556    // test correct indent after newline in brackets
25557    cx.set_state(indoc! {"
25558        {ˇ}
25559    "});
25560    cx.update_editor(|editor, window, cx| {
25561        editor.newline(&Newline, window, cx);
25562    });
25563    cx.run_until_parked();
25564    cx.assert_editor_state(indoc! {"
25565        {
25566            ˇ
25567        }
25568    "});
25569
25570    cx.set_state(indoc! {"
25571        (ˇ)
25572    "});
25573    cx.update_editor(|editor, window, cx| {
25574        editor.newline(&Newline, window, cx);
25575    });
25576    cx.run_until_parked();
25577    cx.assert_editor_state(indoc! {"
25578        (
25579            ˇ
25580        )
25581    "});
25582
25583    // do not indent after empty lists or dictionaries
25584    cx.set_state(indoc! {"
25585        a = []ˇ
25586    "});
25587    cx.update_editor(|editor, window, cx| {
25588        editor.newline(&Newline, window, cx);
25589    });
25590    cx.run_until_parked();
25591    cx.assert_editor_state(indoc! {"
25592        a = []
25593        ˇ
25594    "});
25595}
25596
25597#[gpui::test]
25598async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25599    init_test(cx, |_| {});
25600
25601    let mut cx = EditorTestContext::new(cx).await;
25602    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25603    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25604
25605    // test cursor move to start of each line on tab
25606    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25607    cx.set_state(indoc! {"
25608        function main() {
25609        ˇ    for item in $items; do
25610        ˇ        while [ -n \"$item\" ]; do
25611        ˇ            if [ \"$value\" -gt 10 ]; then
25612        ˇ                continue
25613        ˇ            elif [ \"$value\" -lt 0 ]; then
25614        ˇ                break
25615        ˇ            else
25616        ˇ                echo \"$item\"
25617        ˇ            fi
25618        ˇ        done
25619        ˇ    done
25620        ˇ}
25621    "});
25622    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25623    cx.assert_editor_state(indoc! {"
25624        function main() {
25625            ˇfor item in $items; do
25626                ˇwhile [ -n \"$item\" ]; do
25627                    ˇif [ \"$value\" -gt 10 ]; then
25628                        ˇcontinue
25629                    ˇelif [ \"$value\" -lt 0 ]; then
25630                        ˇbreak
25631                    ˇelse
25632                        ˇecho \"$item\"
25633                    ˇfi
25634                ˇdone
25635            ˇdone
25636        ˇ}
25637    "});
25638    // test relative indent is preserved when tab
25639    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25640    cx.assert_editor_state(indoc! {"
25641        function main() {
25642                ˇfor item in $items; do
25643                    ˇwhile [ -n \"$item\" ]; do
25644                        ˇif [ \"$value\" -gt 10 ]; then
25645                            ˇcontinue
25646                        ˇelif [ \"$value\" -lt 0 ]; then
25647                            ˇbreak
25648                        ˇelse
25649                            ˇecho \"$item\"
25650                        ˇfi
25651                    ˇdone
25652                ˇdone
25653            ˇ}
25654    "});
25655
25656    // test cursor move to start of each line on tab
25657    // for `case` statement with patterns
25658    cx.set_state(indoc! {"
25659        function handle() {
25660        ˇ    case \"$1\" in
25661        ˇ        start)
25662        ˇ            echo \"a\"
25663        ˇ            ;;
25664        ˇ        stop)
25665        ˇ            echo \"b\"
25666        ˇ            ;;
25667        ˇ        *)
25668        ˇ            echo \"c\"
25669        ˇ            ;;
25670        ˇ    esac
25671        ˇ}
25672    "});
25673    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25674    cx.assert_editor_state(indoc! {"
25675        function handle() {
25676            ˇcase \"$1\" in
25677                ˇstart)
25678                    ˇecho \"a\"
25679                    ˇ;;
25680                ˇstop)
25681                    ˇecho \"b\"
25682                    ˇ;;
25683                ˇ*)
25684                    ˇecho \"c\"
25685                    ˇ;;
25686            ˇesac
25687        ˇ}
25688    "});
25689}
25690
25691#[gpui::test]
25692async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25693    init_test(cx, |_| {});
25694
25695    let mut cx = EditorTestContext::new(cx).await;
25696    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25697    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25698
25699    // test indents on comment insert
25700    cx.set_state(indoc! {"
25701        function main() {
25702        ˇ    for item in $items; do
25703        ˇ        while [ -n \"$item\" ]; do
25704        ˇ            if [ \"$value\" -gt 10 ]; then
25705        ˇ                continue
25706        ˇ            elif [ \"$value\" -lt 0 ]; then
25707        ˇ                break
25708        ˇ            else
25709        ˇ                echo \"$item\"
25710        ˇ            fi
25711        ˇ        done
25712        ˇ    done
25713        ˇ}
25714    "});
25715    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25716    cx.assert_editor_state(indoc! {"
25717        function main() {
25718        #ˇ    for item in $items; do
25719        #ˇ        while [ -n \"$item\" ]; do
25720        #ˇ            if [ \"$value\" -gt 10 ]; then
25721        #ˇ                continue
25722        #ˇ            elif [ \"$value\" -lt 0 ]; then
25723        #ˇ                break
25724        #ˇ            else
25725        #ˇ                echo \"$item\"
25726        #ˇ            fi
25727        #ˇ        done
25728        #ˇ    done
25729        #ˇ}
25730    "});
25731}
25732
25733#[gpui::test]
25734async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25735    init_test(cx, |_| {});
25736
25737    let mut cx = EditorTestContext::new(cx).await;
25738    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25739    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25740
25741    // test `else` auto outdents when typed inside `if` block
25742    cx.set_state(indoc! {"
25743        if [ \"$1\" = \"test\" ]; then
25744            echo \"foo bar\"
25745            ˇ
25746    "});
25747    cx.update_editor(|editor, window, cx| {
25748        editor.handle_input("else", window, cx);
25749    });
25750    cx.assert_editor_state(indoc! {"
25751        if [ \"$1\" = \"test\" ]; then
25752            echo \"foo bar\"
25753        elseˇ
25754    "});
25755
25756    // test `elif` auto outdents when typed inside `if` block
25757    cx.set_state(indoc! {"
25758        if [ \"$1\" = \"test\" ]; then
25759            echo \"foo bar\"
25760            ˇ
25761    "});
25762    cx.update_editor(|editor, window, cx| {
25763        editor.handle_input("elif", window, cx);
25764    });
25765    cx.assert_editor_state(indoc! {"
25766        if [ \"$1\" = \"test\" ]; then
25767            echo \"foo bar\"
25768        elifˇ
25769    "});
25770
25771    // test `fi` auto outdents when typed inside `else` block
25772    cx.set_state(indoc! {"
25773        if [ \"$1\" = \"test\" ]; then
25774            echo \"foo bar\"
25775        else
25776            echo \"bar baz\"
25777            ˇ
25778    "});
25779    cx.update_editor(|editor, window, cx| {
25780        editor.handle_input("fi", window, cx);
25781    });
25782    cx.assert_editor_state(indoc! {"
25783        if [ \"$1\" = \"test\" ]; then
25784            echo \"foo bar\"
25785        else
25786            echo \"bar baz\"
25787        fiˇ
25788    "});
25789
25790    // test `done` auto outdents when typed inside `while` block
25791    cx.set_state(indoc! {"
25792        while read line; do
25793            echo \"$line\"
25794            ˇ
25795    "});
25796    cx.update_editor(|editor, window, cx| {
25797        editor.handle_input("done", window, cx);
25798    });
25799    cx.assert_editor_state(indoc! {"
25800        while read line; do
25801            echo \"$line\"
25802        doneˇ
25803    "});
25804
25805    // test `done` auto outdents when typed inside `for` block
25806    cx.set_state(indoc! {"
25807        for file in *.txt; do
25808            cat \"$file\"
25809            ˇ
25810    "});
25811    cx.update_editor(|editor, window, cx| {
25812        editor.handle_input("done", window, cx);
25813    });
25814    cx.assert_editor_state(indoc! {"
25815        for file in *.txt; do
25816            cat \"$file\"
25817        doneˇ
25818    "});
25819
25820    // test `esac` auto outdents when typed inside `case` block
25821    cx.set_state(indoc! {"
25822        case \"$1\" in
25823            start)
25824                echo \"foo bar\"
25825                ;;
25826            stop)
25827                echo \"bar baz\"
25828                ;;
25829            ˇ
25830    "});
25831    cx.update_editor(|editor, window, cx| {
25832        editor.handle_input("esac", window, cx);
25833    });
25834    cx.assert_editor_state(indoc! {"
25835        case \"$1\" in
25836            start)
25837                echo \"foo bar\"
25838                ;;
25839            stop)
25840                echo \"bar baz\"
25841                ;;
25842        esacˇ
25843    "});
25844
25845    // test `*)` auto outdents when typed inside `case` block
25846    cx.set_state(indoc! {"
25847        case \"$1\" in
25848            start)
25849                echo \"foo bar\"
25850                ;;
25851                ˇ
25852    "});
25853    cx.update_editor(|editor, window, cx| {
25854        editor.handle_input("*)", window, cx);
25855    });
25856    cx.assert_editor_state(indoc! {"
25857        case \"$1\" in
25858            start)
25859                echo \"foo bar\"
25860                ;;
25861            *)ˇ
25862    "});
25863
25864    // test `fi` outdents to correct level with nested if blocks
25865    cx.set_state(indoc! {"
25866        if [ \"$1\" = \"test\" ]; then
25867            echo \"outer if\"
25868            if [ \"$2\" = \"debug\" ]; then
25869                echo \"inner if\"
25870                ˇ
25871    "});
25872    cx.update_editor(|editor, window, cx| {
25873        editor.handle_input("fi", window, cx);
25874    });
25875    cx.assert_editor_state(indoc! {"
25876        if [ \"$1\" = \"test\" ]; then
25877            echo \"outer if\"
25878            if [ \"$2\" = \"debug\" ]; then
25879                echo \"inner if\"
25880            fiˇ
25881    "});
25882}
25883
25884#[gpui::test]
25885async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25886    init_test(cx, |_| {});
25887    update_test_language_settings(cx, |settings| {
25888        settings.defaults.extend_comment_on_newline = Some(false);
25889    });
25890    let mut cx = EditorTestContext::new(cx).await;
25891    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25892    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25893
25894    // test correct indent after newline on comment
25895    cx.set_state(indoc! {"
25896        # COMMENT:ˇ
25897    "});
25898    cx.update_editor(|editor, window, cx| {
25899        editor.newline(&Newline, window, cx);
25900    });
25901    cx.assert_editor_state(indoc! {"
25902        # COMMENT:
25903        ˇ
25904    "});
25905
25906    // test correct indent after newline after `then`
25907    cx.set_state(indoc! {"
25908
25909        if [ \"$1\" = \"test\" ]; thenˇ
25910    "});
25911    cx.update_editor(|editor, window, cx| {
25912        editor.newline(&Newline, window, cx);
25913    });
25914    cx.run_until_parked();
25915    cx.assert_editor_state(indoc! {"
25916
25917        if [ \"$1\" = \"test\" ]; then
25918            ˇ
25919    "});
25920
25921    // test correct indent after newline after `else`
25922    cx.set_state(indoc! {"
25923        if [ \"$1\" = \"test\" ]; then
25924        elseˇ
25925    "});
25926    cx.update_editor(|editor, window, cx| {
25927        editor.newline(&Newline, window, cx);
25928    });
25929    cx.run_until_parked();
25930    cx.assert_editor_state(indoc! {"
25931        if [ \"$1\" = \"test\" ]; then
25932        else
25933            ˇ
25934    "});
25935
25936    // test correct indent after newline after `elif`
25937    cx.set_state(indoc! {"
25938        if [ \"$1\" = \"test\" ]; then
25939        elifˇ
25940    "});
25941    cx.update_editor(|editor, window, cx| {
25942        editor.newline(&Newline, window, cx);
25943    });
25944    cx.run_until_parked();
25945    cx.assert_editor_state(indoc! {"
25946        if [ \"$1\" = \"test\" ]; then
25947        elif
25948            ˇ
25949    "});
25950
25951    // test correct indent after newline after `do`
25952    cx.set_state(indoc! {"
25953        for file in *.txt; doˇ
25954    "});
25955    cx.update_editor(|editor, window, cx| {
25956        editor.newline(&Newline, window, cx);
25957    });
25958    cx.run_until_parked();
25959    cx.assert_editor_state(indoc! {"
25960        for file in *.txt; do
25961            ˇ
25962    "});
25963
25964    // test correct indent after newline after case pattern
25965    cx.set_state(indoc! {"
25966        case \"$1\" in
25967            start)ˇ
25968    "});
25969    cx.update_editor(|editor, window, cx| {
25970        editor.newline(&Newline, window, cx);
25971    });
25972    cx.run_until_parked();
25973    cx.assert_editor_state(indoc! {"
25974        case \"$1\" in
25975            start)
25976                ˇ
25977    "});
25978
25979    // test correct indent after newline after case pattern
25980    cx.set_state(indoc! {"
25981        case \"$1\" in
25982            start)
25983                ;;
25984            *)ˇ
25985    "});
25986    cx.update_editor(|editor, window, cx| {
25987        editor.newline(&Newline, window, cx);
25988    });
25989    cx.run_until_parked();
25990    cx.assert_editor_state(indoc! {"
25991        case \"$1\" in
25992            start)
25993                ;;
25994            *)
25995                ˇ
25996    "});
25997
25998    // test correct indent after newline after function opening brace
25999    cx.set_state(indoc! {"
26000        function test() {ˇ}
26001    "});
26002    cx.update_editor(|editor, window, cx| {
26003        editor.newline(&Newline, window, cx);
26004    });
26005    cx.run_until_parked();
26006    cx.assert_editor_state(indoc! {"
26007        function test() {
26008            ˇ
26009        }
26010    "});
26011
26012    // test no extra indent after semicolon on same line
26013    cx.set_state(indoc! {"
26014        echo \"test\"26015    "});
26016    cx.update_editor(|editor, window, cx| {
26017        editor.newline(&Newline, window, cx);
26018    });
26019    cx.run_until_parked();
26020    cx.assert_editor_state(indoc! {"
26021        echo \"test\";
26022        ˇ
26023    "});
26024}
26025
26026fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26027    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26028    point..point
26029}
26030
26031#[track_caller]
26032fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26033    let (text, ranges) = marked_text_ranges(marked_text, true);
26034    assert_eq!(editor.text(cx), text);
26035    assert_eq!(
26036        editor.selections.ranges(&editor.display_snapshot(cx)),
26037        ranges
26038            .iter()
26039            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26040            .collect::<Vec<_>>(),
26041        "Assert selections are {}",
26042        marked_text
26043    );
26044}
26045
26046pub fn handle_signature_help_request(
26047    cx: &mut EditorLspTestContext,
26048    mocked_response: lsp::SignatureHelp,
26049) -> impl Future<Output = ()> + use<> {
26050    let mut request =
26051        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26052            let mocked_response = mocked_response.clone();
26053            async move { Ok(Some(mocked_response)) }
26054        });
26055
26056    async move {
26057        request.next().await;
26058    }
26059}
26060
26061#[track_caller]
26062pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
26063    cx.update_editor(|editor, _, _| {
26064        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
26065            let entries = menu.entries.borrow();
26066            let entries = entries
26067                .iter()
26068                .map(|entry| entry.string.as_str())
26069                .collect::<Vec<_>>();
26070            assert_eq!(entries, expected);
26071        } else {
26072            panic!("Expected completions menu");
26073        }
26074    });
26075}
26076
26077#[gpui::test]
26078async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
26079    init_test(cx, |_| {});
26080    let mut cx = EditorLspTestContext::new_rust(
26081        lsp::ServerCapabilities {
26082            completion_provider: Some(lsp::CompletionOptions {
26083                ..Default::default()
26084            }),
26085            ..Default::default()
26086        },
26087        cx,
26088    )
26089    .await;
26090    cx.lsp
26091        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26092            Ok(Some(lsp::CompletionResponse::Array(vec![
26093                lsp::CompletionItem {
26094                    label: "unsafe".into(),
26095                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26096                        range: lsp::Range {
26097                            start: lsp::Position {
26098                                line: 0,
26099                                character: 9,
26100                            },
26101                            end: lsp::Position {
26102                                line: 0,
26103                                character: 11,
26104                            },
26105                        },
26106                        new_text: "unsafe".to_string(),
26107                    })),
26108                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
26109                    ..Default::default()
26110                },
26111            ])))
26112        });
26113
26114    cx.update_editor(|editor, _, cx| {
26115        editor.project().unwrap().update(cx, |project, cx| {
26116            project.snippets().update(cx, |snippets, _cx| {
26117                snippets.add_snippet_for_test(
26118                    None,
26119                    PathBuf::from("test_snippets.json"),
26120                    vec![
26121                        Arc::new(project::snippet_provider::Snippet {
26122                            prefix: vec![
26123                                "unlimited word count".to_string(),
26124                                "unlimit word count".to_string(),
26125                                "unlimited unknown".to_string(),
26126                            ],
26127                            body: "this is many words".to_string(),
26128                            description: Some("description".to_string()),
26129                            name: "multi-word snippet test".to_string(),
26130                        }),
26131                        Arc::new(project::snippet_provider::Snippet {
26132                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
26133                            body: "fewer words".to_string(),
26134                            description: Some("alt description".to_string()),
26135                            name: "other name".to_string(),
26136                        }),
26137                        Arc::new(project::snippet_provider::Snippet {
26138                            prefix: vec!["ab aa".to_string()],
26139                            body: "abcd".to_string(),
26140                            description: None,
26141                            name: "alphabet".to_string(),
26142                        }),
26143                    ],
26144                );
26145            });
26146        })
26147    });
26148
26149    let get_completions = |cx: &mut EditorLspTestContext| {
26150        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
26151            Some(CodeContextMenu::Completions(context_menu)) => {
26152                let entries = context_menu.entries.borrow();
26153                entries
26154                    .iter()
26155                    .map(|entry| entry.string.clone())
26156                    .collect_vec()
26157            }
26158            _ => vec![],
26159        })
26160    };
26161
26162    // snippets:
26163    //  @foo
26164    //  foo bar
26165    //
26166    // when typing:
26167    //
26168    // when typing:
26169    //  - if I type a symbol "open the completions with snippets only"
26170    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26171    //
26172    // stuff we need:
26173    //  - filtering logic change?
26174    //  - remember how far back the completion started.
26175
26176    let test_cases: &[(&str, &[&str])] = &[
26177        (
26178            "un",
26179            &[
26180                "unsafe",
26181                "unlimit word count",
26182                "unlimited unknown",
26183                "unlimited word count",
26184                "unsnip",
26185            ],
26186        ),
26187        (
26188            "u ",
26189            &[
26190                "unlimit word count",
26191                "unlimited unknown",
26192                "unlimited word count",
26193            ],
26194        ),
26195        ("u a", &["ab aa", "unsafe"]), // unsAfe
26196        (
26197            "u u",
26198            &[
26199                "unsafe",
26200                "unlimit word count",
26201                "unlimited unknown", // ranked highest among snippets
26202                "unlimited word count",
26203                "unsnip",
26204            ],
26205        ),
26206        ("uw c", &["unlimit word count", "unlimited word count"]),
26207        (
26208            "u w",
26209            &[
26210                "unlimit word count",
26211                "unlimited word count",
26212                "unlimited unknown",
26213            ],
26214        ),
26215        ("u w ", &["unlimit word count", "unlimited word count"]),
26216        (
26217            "u ",
26218            &[
26219                "unlimit word count",
26220                "unlimited unknown",
26221                "unlimited word count",
26222            ],
26223        ),
26224        ("wor", &[]),
26225        ("uf", &["unsafe"]),
26226        ("af", &["unsafe"]),
26227        ("afu", &[]),
26228        (
26229            "ue",
26230            &["unsafe", "unlimited unknown", "unlimited word count"],
26231        ),
26232        ("@", &["@few"]),
26233        ("@few", &["@few"]),
26234        ("@ ", &[]),
26235        ("a@", &["@few"]),
26236        ("a@f", &["@few", "unsafe"]),
26237        ("a@fw", &["@few"]),
26238        ("a", &["ab aa", "unsafe"]),
26239        ("aa", &["ab aa"]),
26240        ("aaa", &["ab aa"]),
26241        ("ab", &["ab aa"]),
26242        ("ab ", &["ab aa"]),
26243        ("ab a", &["ab aa", "unsafe"]),
26244        ("ab ab", &["ab aa"]),
26245        ("ab ab aa", &["ab aa"]),
26246    ];
26247
26248    for &(input_to_simulate, expected_completions) in test_cases {
26249        cx.set_state("fn a() { ˇ }\n");
26250        for c in input_to_simulate.split("") {
26251            cx.simulate_input(c);
26252            cx.run_until_parked();
26253        }
26254        let expected_completions = expected_completions
26255            .iter()
26256            .map(|s| s.to_string())
26257            .collect_vec();
26258        assert_eq!(
26259            get_completions(&mut cx),
26260            expected_completions,
26261            "< actual / expected >, input = {input_to_simulate:?}",
26262        );
26263    }
26264}
26265
26266/// Handle completion request passing a marked string specifying where the completion
26267/// should be triggered from using '|' character, what range should be replaced, and what completions
26268/// should be returned using '<' and '>' to delimit the range.
26269///
26270/// Also see `handle_completion_request_with_insert_and_replace`.
26271#[track_caller]
26272pub fn handle_completion_request(
26273    marked_string: &str,
26274    completions: Vec<&'static str>,
26275    is_incomplete: bool,
26276    counter: Arc<AtomicUsize>,
26277    cx: &mut EditorLspTestContext,
26278) -> impl Future<Output = ()> {
26279    let complete_from_marker: TextRangeMarker = '|'.into();
26280    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26281    let (_, mut marked_ranges) = marked_text_ranges_by(
26282        marked_string,
26283        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26284    );
26285
26286    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26287        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26288    ));
26289    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26290    let replace_range =
26291        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26292
26293    let mut request =
26294        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26295            let completions = completions.clone();
26296            counter.fetch_add(1, atomic::Ordering::Release);
26297            async move {
26298                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26299                assert_eq!(
26300                    params.text_document_position.position,
26301                    complete_from_position
26302                );
26303                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26304                    is_incomplete,
26305                    item_defaults: None,
26306                    items: completions
26307                        .iter()
26308                        .map(|completion_text| lsp::CompletionItem {
26309                            label: completion_text.to_string(),
26310                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26311                                range: replace_range,
26312                                new_text: completion_text.to_string(),
26313                            })),
26314                            ..Default::default()
26315                        })
26316                        .collect(),
26317                })))
26318            }
26319        });
26320
26321    async move {
26322        request.next().await;
26323    }
26324}
26325
26326/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26327/// given instead, which also contains an `insert` range.
26328///
26329/// This function uses markers to define ranges:
26330/// - `|` marks the cursor position
26331/// - `<>` marks the replace range
26332/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26333pub fn handle_completion_request_with_insert_and_replace(
26334    cx: &mut EditorLspTestContext,
26335    marked_string: &str,
26336    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26337    counter: Arc<AtomicUsize>,
26338) -> impl Future<Output = ()> {
26339    let complete_from_marker: TextRangeMarker = '|'.into();
26340    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26341    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26342
26343    let (_, mut marked_ranges) = marked_text_ranges_by(
26344        marked_string,
26345        vec![
26346            complete_from_marker.clone(),
26347            replace_range_marker.clone(),
26348            insert_range_marker.clone(),
26349        ],
26350    );
26351
26352    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26353        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26354    ));
26355    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26356    let replace_range =
26357        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26358
26359    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26360        Some(ranges) if !ranges.is_empty() => {
26361            let range1 = ranges[0].clone();
26362            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26363        }
26364        _ => lsp::Range {
26365            start: replace_range.start,
26366            end: complete_from_position,
26367        },
26368    };
26369
26370    let mut request =
26371        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26372            let completions = completions.clone();
26373            counter.fetch_add(1, atomic::Ordering::Release);
26374            async move {
26375                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26376                assert_eq!(
26377                    params.text_document_position.position, complete_from_position,
26378                    "marker `|` position doesn't match",
26379                );
26380                Ok(Some(lsp::CompletionResponse::Array(
26381                    completions
26382                        .iter()
26383                        .map(|(label, new_text)| lsp::CompletionItem {
26384                            label: label.to_string(),
26385                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26386                                lsp::InsertReplaceEdit {
26387                                    insert: insert_range,
26388                                    replace: replace_range,
26389                                    new_text: new_text.to_string(),
26390                                },
26391                            )),
26392                            ..Default::default()
26393                        })
26394                        .collect(),
26395                )))
26396            }
26397        });
26398
26399    async move {
26400        request.next().await;
26401    }
26402}
26403
26404fn handle_resolve_completion_request(
26405    cx: &mut EditorLspTestContext,
26406    edits: Option<Vec<(&'static str, &'static str)>>,
26407) -> impl Future<Output = ()> {
26408    let edits = edits.map(|edits| {
26409        edits
26410            .iter()
26411            .map(|(marked_string, new_text)| {
26412                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26413                let replace_range = cx.to_lsp_range(
26414                    MultiBufferOffset(marked_ranges[0].start)
26415                        ..MultiBufferOffset(marked_ranges[0].end),
26416                );
26417                lsp::TextEdit::new(replace_range, new_text.to_string())
26418            })
26419            .collect::<Vec<_>>()
26420    });
26421
26422    let mut request =
26423        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26424            let edits = edits.clone();
26425            async move {
26426                Ok(lsp::CompletionItem {
26427                    additional_text_edits: edits,
26428                    ..Default::default()
26429                })
26430            }
26431        });
26432
26433    async move {
26434        request.next().await;
26435    }
26436}
26437
26438pub(crate) fn update_test_language_settings(
26439    cx: &mut TestAppContext,
26440    f: impl Fn(&mut AllLanguageSettingsContent),
26441) {
26442    cx.update(|cx| {
26443        SettingsStore::update_global(cx, |store, cx| {
26444            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26445        });
26446    });
26447}
26448
26449pub(crate) fn update_test_project_settings(
26450    cx: &mut TestAppContext,
26451    f: impl Fn(&mut ProjectSettingsContent),
26452) {
26453    cx.update(|cx| {
26454        SettingsStore::update_global(cx, |store, cx| {
26455            store.update_user_settings(cx, |settings| f(&mut settings.project));
26456        });
26457    });
26458}
26459
26460pub(crate) fn update_test_editor_settings(
26461    cx: &mut TestAppContext,
26462    f: impl Fn(&mut EditorSettingsContent),
26463) {
26464    cx.update(|cx| {
26465        SettingsStore::update_global(cx, |store, cx| {
26466            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26467        })
26468    })
26469}
26470
26471pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26472    cx.update(|cx| {
26473        assets::Assets.load_test_fonts(cx);
26474        let store = SettingsStore::test(cx);
26475        cx.set_global(store);
26476        theme::init(theme::LoadThemes::JustBase, cx);
26477        release_channel::init(semver::Version::new(0, 0, 0), cx);
26478        crate::init(cx);
26479    });
26480    zlog::init_test();
26481    update_test_language_settings(cx, f);
26482}
26483
26484#[track_caller]
26485fn assert_hunk_revert(
26486    not_reverted_text_with_selections: &str,
26487    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
26488    expected_reverted_text_with_selections: &str,
26489    base_text: &str,
26490    cx: &mut EditorLspTestContext,
26491) {
26492    cx.set_state(not_reverted_text_with_selections);
26493    cx.set_head_text(base_text);
26494    cx.executor().run_until_parked();
26495
26496    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
26497        let snapshot = editor.snapshot(window, cx);
26498        let reverted_hunk_statuses = snapshot
26499            .buffer_snapshot()
26500            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
26501            .map(|hunk| hunk.status().kind)
26502            .collect::<Vec<_>>();
26503
26504        editor.git_restore(&Default::default(), window, cx);
26505        reverted_hunk_statuses
26506    });
26507    cx.executor().run_until_parked();
26508    cx.assert_editor_state(expected_reverted_text_with_selections);
26509    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
26510}
26511
26512#[gpui::test(iterations = 10)]
26513async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
26514    init_test(cx, |_| {});
26515
26516    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
26517    let counter = diagnostic_requests.clone();
26518
26519    let fs = FakeFs::new(cx.executor());
26520    fs.insert_tree(
26521        path!("/a"),
26522        json!({
26523            "first.rs": "fn main() { let a = 5; }",
26524            "second.rs": "// Test file",
26525        }),
26526    )
26527    .await;
26528
26529    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26530    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26531    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26532
26533    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26534    language_registry.add(rust_lang());
26535    let mut fake_servers = language_registry.register_fake_lsp(
26536        "Rust",
26537        FakeLspAdapter {
26538            capabilities: lsp::ServerCapabilities {
26539                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
26540                    lsp::DiagnosticOptions {
26541                        identifier: None,
26542                        inter_file_dependencies: true,
26543                        workspace_diagnostics: true,
26544                        work_done_progress_options: Default::default(),
26545                    },
26546                )),
26547                ..Default::default()
26548            },
26549            ..Default::default()
26550        },
26551    );
26552
26553    let editor = workspace
26554        .update(cx, |workspace, window, cx| {
26555            workspace.open_abs_path(
26556                PathBuf::from(path!("/a/first.rs")),
26557                OpenOptions::default(),
26558                window,
26559                cx,
26560            )
26561        })
26562        .unwrap()
26563        .await
26564        .unwrap()
26565        .downcast::<Editor>()
26566        .unwrap();
26567    let fake_server = fake_servers.next().await.unwrap();
26568    let server_id = fake_server.server.server_id();
26569    let mut first_request = fake_server
26570        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
26571            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
26572            let result_id = Some(new_result_id.to_string());
26573            assert_eq!(
26574                params.text_document.uri,
26575                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26576            );
26577            async move {
26578                Ok(lsp::DocumentDiagnosticReportResult::Report(
26579                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
26580                        related_documents: None,
26581                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
26582                            items: Vec::new(),
26583                            result_id,
26584                        },
26585                    }),
26586                ))
26587            }
26588        });
26589
26590    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
26591        project.update(cx, |project, cx| {
26592            let buffer_id = editor
26593                .read(cx)
26594                .buffer()
26595                .read(cx)
26596                .as_singleton()
26597                .expect("created a singleton buffer")
26598                .read(cx)
26599                .remote_id();
26600            let buffer_result_id = project
26601                .lsp_store()
26602                .read(cx)
26603                .result_id(server_id, buffer_id, cx);
26604            assert_eq!(expected, buffer_result_id);
26605        });
26606    };
26607
26608    ensure_result_id(None, cx);
26609    cx.executor().advance_clock(Duration::from_millis(60));
26610    cx.executor().run_until_parked();
26611    assert_eq!(
26612        diagnostic_requests.load(atomic::Ordering::Acquire),
26613        1,
26614        "Opening file should trigger diagnostic request"
26615    );
26616    first_request
26617        .next()
26618        .await
26619        .expect("should have sent the first diagnostics pull request");
26620    ensure_result_id(Some("1".to_string()), cx);
26621
26622    // Editing should trigger diagnostics
26623    editor.update_in(cx, |editor, window, cx| {
26624        editor.handle_input("2", window, cx)
26625    });
26626    cx.executor().advance_clock(Duration::from_millis(60));
26627    cx.executor().run_until_parked();
26628    assert_eq!(
26629        diagnostic_requests.load(atomic::Ordering::Acquire),
26630        2,
26631        "Editing should trigger diagnostic request"
26632    );
26633    ensure_result_id(Some("2".to_string()), cx);
26634
26635    // Moving cursor should not trigger diagnostic request
26636    editor.update_in(cx, |editor, window, cx| {
26637        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26638            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
26639        });
26640    });
26641    cx.executor().advance_clock(Duration::from_millis(60));
26642    cx.executor().run_until_parked();
26643    assert_eq!(
26644        diagnostic_requests.load(atomic::Ordering::Acquire),
26645        2,
26646        "Cursor movement should not trigger diagnostic request"
26647    );
26648    ensure_result_id(Some("2".to_string()), cx);
26649    // Multiple rapid edits should be debounced
26650    for _ in 0..5 {
26651        editor.update_in(cx, |editor, window, cx| {
26652            editor.handle_input("x", window, cx)
26653        });
26654    }
26655    cx.executor().advance_clock(Duration::from_millis(60));
26656    cx.executor().run_until_parked();
26657
26658    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
26659    assert!(
26660        final_requests <= 4,
26661        "Multiple rapid edits should be debounced (got {final_requests} requests)",
26662    );
26663    ensure_result_id(Some(final_requests.to_string()), cx);
26664}
26665
26666#[gpui::test]
26667async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
26668    // Regression test for issue #11671
26669    // Previously, adding a cursor after moving multiple cursors would reset
26670    // the cursor count instead of adding to the existing cursors.
26671    init_test(cx, |_| {});
26672    let mut cx = EditorTestContext::new(cx).await;
26673
26674    // Create a simple buffer with cursor at start
26675    cx.set_state(indoc! {"
26676        ˇaaaa
26677        bbbb
26678        cccc
26679        dddd
26680        eeee
26681        ffff
26682        gggg
26683        hhhh"});
26684
26685    // Add 2 cursors below (so we have 3 total)
26686    cx.update_editor(|editor, window, cx| {
26687        editor.add_selection_below(&Default::default(), window, cx);
26688        editor.add_selection_below(&Default::default(), window, cx);
26689    });
26690
26691    // Verify we have 3 cursors
26692    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
26693    assert_eq!(
26694        initial_count, 3,
26695        "Should have 3 cursors after adding 2 below"
26696    );
26697
26698    // Move down one line
26699    cx.update_editor(|editor, window, cx| {
26700        editor.move_down(&MoveDown, window, cx);
26701    });
26702
26703    // Add another cursor below
26704    cx.update_editor(|editor, window, cx| {
26705        editor.add_selection_below(&Default::default(), window, cx);
26706    });
26707
26708    // Should now have 4 cursors (3 original + 1 new)
26709    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
26710    assert_eq!(
26711        final_count, 4,
26712        "Should have 4 cursors after moving and adding another"
26713    );
26714}
26715
26716#[gpui::test]
26717async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
26718    init_test(cx, |_| {});
26719
26720    let mut cx = EditorTestContext::new(cx).await;
26721
26722    cx.set_state(indoc!(
26723        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
26724           Second line here"#
26725    ));
26726
26727    cx.update_editor(|editor, window, cx| {
26728        // Enable soft wrapping with a narrow width to force soft wrapping and
26729        // confirm that more than 2 rows are being displayed.
26730        editor.set_wrap_width(Some(100.0.into()), cx);
26731        assert!(editor.display_text(cx).lines().count() > 2);
26732
26733        editor.add_selection_below(
26734            &AddSelectionBelow {
26735                skip_soft_wrap: true,
26736            },
26737            window,
26738            cx,
26739        );
26740
26741        assert_eq!(
26742            display_ranges(editor, cx),
26743            &[
26744                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26745                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
26746            ]
26747        );
26748
26749        editor.add_selection_above(
26750            &AddSelectionAbove {
26751                skip_soft_wrap: true,
26752            },
26753            window,
26754            cx,
26755        );
26756
26757        assert_eq!(
26758            display_ranges(editor, cx),
26759            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26760        );
26761
26762        editor.add_selection_below(
26763            &AddSelectionBelow {
26764                skip_soft_wrap: false,
26765            },
26766            window,
26767            cx,
26768        );
26769
26770        assert_eq!(
26771            display_ranges(editor, cx),
26772            &[
26773                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26774                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26775            ]
26776        );
26777
26778        editor.add_selection_above(
26779            &AddSelectionAbove {
26780                skip_soft_wrap: false,
26781            },
26782            window,
26783            cx,
26784        );
26785
26786        assert_eq!(
26787            display_ranges(editor, cx),
26788            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26789        );
26790    });
26791}
26792
26793#[gpui::test(iterations = 10)]
26794async fn test_document_colors(cx: &mut TestAppContext) {
26795    let expected_color = Rgba {
26796        r: 0.33,
26797        g: 0.33,
26798        b: 0.33,
26799        a: 0.33,
26800    };
26801
26802    init_test(cx, |_| {});
26803
26804    let fs = FakeFs::new(cx.executor());
26805    fs.insert_tree(
26806        path!("/a"),
26807        json!({
26808            "first.rs": "fn main() { let a = 5; }",
26809        }),
26810    )
26811    .await;
26812
26813    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26814    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26815    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26816
26817    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26818    language_registry.add(rust_lang());
26819    let mut fake_servers = language_registry.register_fake_lsp(
26820        "Rust",
26821        FakeLspAdapter {
26822            capabilities: lsp::ServerCapabilities {
26823                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26824                ..lsp::ServerCapabilities::default()
26825            },
26826            name: "rust-analyzer",
26827            ..FakeLspAdapter::default()
26828        },
26829    );
26830    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26831        "Rust",
26832        FakeLspAdapter {
26833            capabilities: lsp::ServerCapabilities {
26834                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26835                ..lsp::ServerCapabilities::default()
26836            },
26837            name: "not-rust-analyzer",
26838            ..FakeLspAdapter::default()
26839        },
26840    );
26841
26842    let editor = workspace
26843        .update(cx, |workspace, window, cx| {
26844            workspace.open_abs_path(
26845                PathBuf::from(path!("/a/first.rs")),
26846                OpenOptions::default(),
26847                window,
26848                cx,
26849            )
26850        })
26851        .unwrap()
26852        .await
26853        .unwrap()
26854        .downcast::<Editor>()
26855        .unwrap();
26856    let fake_language_server = fake_servers.next().await.unwrap();
26857    let fake_language_server_without_capabilities =
26858        fake_servers_without_capabilities.next().await.unwrap();
26859    let requests_made = Arc::new(AtomicUsize::new(0));
26860    let closure_requests_made = Arc::clone(&requests_made);
26861    let mut color_request_handle = fake_language_server
26862        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26863            let requests_made = Arc::clone(&closure_requests_made);
26864            async move {
26865                assert_eq!(
26866                    params.text_document.uri,
26867                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26868                );
26869                requests_made.fetch_add(1, atomic::Ordering::Release);
26870                Ok(vec![
26871                    lsp::ColorInformation {
26872                        range: lsp::Range {
26873                            start: lsp::Position {
26874                                line: 0,
26875                                character: 0,
26876                            },
26877                            end: lsp::Position {
26878                                line: 0,
26879                                character: 1,
26880                            },
26881                        },
26882                        color: lsp::Color {
26883                            red: 0.33,
26884                            green: 0.33,
26885                            blue: 0.33,
26886                            alpha: 0.33,
26887                        },
26888                    },
26889                    lsp::ColorInformation {
26890                        range: lsp::Range {
26891                            start: lsp::Position {
26892                                line: 0,
26893                                character: 0,
26894                            },
26895                            end: lsp::Position {
26896                                line: 0,
26897                                character: 1,
26898                            },
26899                        },
26900                        color: lsp::Color {
26901                            red: 0.33,
26902                            green: 0.33,
26903                            blue: 0.33,
26904                            alpha: 0.33,
26905                        },
26906                    },
26907                ])
26908            }
26909        });
26910
26911    let _handle = fake_language_server_without_capabilities
26912        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26913            panic!("Should not be called");
26914        });
26915    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26916    color_request_handle.next().await.unwrap();
26917    cx.run_until_parked();
26918    assert_eq!(
26919        1,
26920        requests_made.load(atomic::Ordering::Acquire),
26921        "Should query for colors once per editor open"
26922    );
26923    editor.update_in(cx, |editor, _, cx| {
26924        assert_eq!(
26925            vec![expected_color],
26926            extract_color_inlays(editor, cx),
26927            "Should have an initial inlay"
26928        );
26929    });
26930
26931    // opening another file in a split should not influence the LSP query counter
26932    workspace
26933        .update(cx, |workspace, window, cx| {
26934            assert_eq!(
26935                workspace.panes().len(),
26936                1,
26937                "Should have one pane with one editor"
26938            );
26939            workspace.move_item_to_pane_in_direction(
26940                &MoveItemToPaneInDirection {
26941                    direction: SplitDirection::Right,
26942                    focus: false,
26943                    clone: true,
26944                },
26945                window,
26946                cx,
26947            );
26948        })
26949        .unwrap();
26950    cx.run_until_parked();
26951    workspace
26952        .update(cx, |workspace, _, cx| {
26953            let panes = workspace.panes();
26954            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26955            for pane in panes {
26956                let editor = pane
26957                    .read(cx)
26958                    .active_item()
26959                    .and_then(|item| item.downcast::<Editor>())
26960                    .expect("Should have opened an editor in each split");
26961                let editor_file = editor
26962                    .read(cx)
26963                    .buffer()
26964                    .read(cx)
26965                    .as_singleton()
26966                    .expect("test deals with singleton buffers")
26967                    .read(cx)
26968                    .file()
26969                    .expect("test buffese should have a file")
26970                    .path();
26971                assert_eq!(
26972                    editor_file.as_ref(),
26973                    rel_path("first.rs"),
26974                    "Both editors should be opened for the same file"
26975                )
26976            }
26977        })
26978        .unwrap();
26979
26980    cx.executor().advance_clock(Duration::from_millis(500));
26981    let save = editor.update_in(cx, |editor, window, cx| {
26982        editor.move_to_end(&MoveToEnd, window, cx);
26983        editor.handle_input("dirty", window, cx);
26984        editor.save(
26985            SaveOptions {
26986                format: true,
26987                autosave: true,
26988            },
26989            project.clone(),
26990            window,
26991            cx,
26992        )
26993    });
26994    save.await.unwrap();
26995
26996    color_request_handle.next().await.unwrap();
26997    cx.run_until_parked();
26998    assert_eq!(
26999        2,
27000        requests_made.load(atomic::Ordering::Acquire),
27001        "Should query for colors once per save (deduplicated) and once per formatting after save"
27002    );
27003
27004    drop(editor);
27005    let close = workspace
27006        .update(cx, |workspace, window, cx| {
27007            workspace.active_pane().update(cx, |pane, cx| {
27008                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27009            })
27010        })
27011        .unwrap();
27012    close.await.unwrap();
27013    let close = workspace
27014        .update(cx, |workspace, window, cx| {
27015            workspace.active_pane().update(cx, |pane, cx| {
27016                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27017            })
27018        })
27019        .unwrap();
27020    close.await.unwrap();
27021    assert_eq!(
27022        2,
27023        requests_made.load(atomic::Ordering::Acquire),
27024        "After saving and closing all editors, no extra requests should be made"
27025    );
27026    workspace
27027        .update(cx, |workspace, _, cx| {
27028            assert!(
27029                workspace.active_item(cx).is_none(),
27030                "Should close all editors"
27031            )
27032        })
27033        .unwrap();
27034
27035    workspace
27036        .update(cx, |workspace, window, cx| {
27037            workspace.active_pane().update(cx, |pane, cx| {
27038                pane.navigate_backward(&workspace::GoBack, window, cx);
27039            })
27040        })
27041        .unwrap();
27042    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27043    cx.run_until_parked();
27044    let editor = workspace
27045        .update(cx, |workspace, _, cx| {
27046            workspace
27047                .active_item(cx)
27048                .expect("Should have reopened the editor again after navigating back")
27049                .downcast::<Editor>()
27050                .expect("Should be an editor")
27051        })
27052        .unwrap();
27053
27054    assert_eq!(
27055        2,
27056        requests_made.load(atomic::Ordering::Acquire),
27057        "Cache should be reused on buffer close and reopen"
27058    );
27059    editor.update(cx, |editor, cx| {
27060        assert_eq!(
27061            vec![expected_color],
27062            extract_color_inlays(editor, cx),
27063            "Should have an initial inlay"
27064        );
27065    });
27066
27067    drop(color_request_handle);
27068    let closure_requests_made = Arc::clone(&requests_made);
27069    let mut empty_color_request_handle = fake_language_server
27070        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27071            let requests_made = Arc::clone(&closure_requests_made);
27072            async move {
27073                assert_eq!(
27074                    params.text_document.uri,
27075                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27076                );
27077                requests_made.fetch_add(1, atomic::Ordering::Release);
27078                Ok(Vec::new())
27079            }
27080        });
27081    let save = editor.update_in(cx, |editor, window, cx| {
27082        editor.move_to_end(&MoveToEnd, window, cx);
27083        editor.handle_input("dirty_again", window, cx);
27084        editor.save(
27085            SaveOptions {
27086                format: false,
27087                autosave: true,
27088            },
27089            project.clone(),
27090            window,
27091            cx,
27092        )
27093    });
27094    save.await.unwrap();
27095
27096    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27097    empty_color_request_handle.next().await.unwrap();
27098    cx.run_until_parked();
27099    assert_eq!(
27100        3,
27101        requests_made.load(atomic::Ordering::Acquire),
27102        "Should query for colors once per save only, as formatting was not requested"
27103    );
27104    editor.update(cx, |editor, cx| {
27105        assert_eq!(
27106            Vec::<Rgba>::new(),
27107            extract_color_inlays(editor, cx),
27108            "Should clear all colors when the server returns an empty response"
27109        );
27110    });
27111}
27112
27113#[gpui::test]
27114async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
27115    init_test(cx, |_| {});
27116    let (editor, cx) = cx.add_window_view(Editor::single_line);
27117    editor.update_in(cx, |editor, window, cx| {
27118        editor.set_text("oops\n\nwow\n", window, cx)
27119    });
27120    cx.run_until_parked();
27121    editor.update(cx, |editor, cx| {
27122        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
27123    });
27124    editor.update(cx, |editor, cx| {
27125        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
27126    });
27127    cx.run_until_parked();
27128    editor.update(cx, |editor, cx| {
27129        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
27130    });
27131}
27132
27133#[gpui::test]
27134async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
27135    init_test(cx, |_| {});
27136
27137    cx.update(|cx| {
27138        register_project_item::<Editor>(cx);
27139    });
27140
27141    let fs = FakeFs::new(cx.executor());
27142    fs.insert_tree("/root1", json!({})).await;
27143    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
27144        .await;
27145
27146    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
27147    let (workspace, cx) =
27148        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
27149
27150    let worktree_id = project.update(cx, |project, cx| {
27151        project.worktrees(cx).next().unwrap().read(cx).id()
27152    });
27153
27154    let handle = workspace
27155        .update_in(cx, |workspace, window, cx| {
27156            let project_path = (worktree_id, rel_path("one.pdf"));
27157            workspace.open_path(project_path, None, true, window, cx)
27158        })
27159        .await
27160        .unwrap();
27161
27162    assert_eq!(
27163        handle.to_any_view().entity_type(),
27164        TypeId::of::<InvalidItemView>()
27165    );
27166}
27167
27168#[gpui::test]
27169async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
27170    init_test(cx, |_| {});
27171
27172    let language = Arc::new(Language::new(
27173        LanguageConfig::default(),
27174        Some(tree_sitter_rust::LANGUAGE.into()),
27175    ));
27176
27177    // Test hierarchical sibling navigation
27178    let text = r#"
27179        fn outer() {
27180            if condition {
27181                let a = 1;
27182            }
27183            let b = 2;
27184        }
27185
27186        fn another() {
27187            let c = 3;
27188        }
27189    "#;
27190
27191    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27192    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27193    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27194
27195    // Wait for parsing to complete
27196    editor
27197        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27198        .await;
27199
27200    editor.update_in(cx, |editor, window, cx| {
27201        // Start by selecting "let a = 1;" inside the if block
27202        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27203            s.select_display_ranges([
27204                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27205            ]);
27206        });
27207
27208        let initial_selection = editor
27209            .selections
27210            .display_ranges(&editor.display_snapshot(cx));
27211        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27212
27213        // Test select next sibling - should move up levels to find the next sibling
27214        // Since "let a = 1;" has no siblings in the if block, it should move up
27215        // to find "let b = 2;" which is a sibling of the if block
27216        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27217        let next_selection = editor
27218            .selections
27219            .display_ranges(&editor.display_snapshot(cx));
27220
27221        // Should have a selection and it should be different from the initial
27222        assert_eq!(
27223            next_selection.len(),
27224            1,
27225            "Should have one selection after next"
27226        );
27227        assert_ne!(
27228            next_selection[0], initial_selection[0],
27229            "Next sibling selection should be different"
27230        );
27231
27232        // Test hierarchical navigation by going to the end of the current function
27233        // and trying to navigate to the next function
27234        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27235            s.select_display_ranges([
27236                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27237            ]);
27238        });
27239
27240        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27241        let function_next_selection = editor
27242            .selections
27243            .display_ranges(&editor.display_snapshot(cx));
27244
27245        // Should move to the next function
27246        assert_eq!(
27247            function_next_selection.len(),
27248            1,
27249            "Should have one selection after function next"
27250        );
27251
27252        // Test select previous sibling navigation
27253        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27254        let prev_selection = editor
27255            .selections
27256            .display_ranges(&editor.display_snapshot(cx));
27257
27258        // Should have a selection and it should be different
27259        assert_eq!(
27260            prev_selection.len(),
27261            1,
27262            "Should have one selection after prev"
27263        );
27264        assert_ne!(
27265            prev_selection[0], function_next_selection[0],
27266            "Previous sibling selection should be different from next"
27267        );
27268    });
27269}
27270
27271#[gpui::test]
27272async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27273    init_test(cx, |_| {});
27274
27275    let mut cx = EditorTestContext::new(cx).await;
27276    cx.set_state(
27277        "let ˇvariable = 42;
27278let another = variable + 1;
27279let result = variable * 2;",
27280    );
27281
27282    // Set up document highlights manually (simulating LSP response)
27283    cx.update_editor(|editor, _window, cx| {
27284        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27285
27286        // Create highlights for "variable" occurrences
27287        let highlight_ranges = [
27288            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27289            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27290            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27291        ];
27292
27293        let anchor_ranges: Vec<_> = highlight_ranges
27294            .iter()
27295            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27296            .collect();
27297
27298        editor.highlight_background::<DocumentHighlightRead>(
27299            &anchor_ranges,
27300            |theme| theme.colors().editor_document_highlight_read_background,
27301            cx,
27302        );
27303    });
27304
27305    // Go to next highlight - should move to second "variable"
27306    cx.update_editor(|editor, window, cx| {
27307        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27308    });
27309    cx.assert_editor_state(
27310        "let variable = 42;
27311let another = ˇvariable + 1;
27312let result = variable * 2;",
27313    );
27314
27315    // Go to next highlight - should move to third "variable"
27316    cx.update_editor(|editor, window, cx| {
27317        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27318    });
27319    cx.assert_editor_state(
27320        "let variable = 42;
27321let another = variable + 1;
27322let result = ˇvariable * 2;",
27323    );
27324
27325    // Go to next highlight - should stay at third "variable" (no wrap-around)
27326    cx.update_editor(|editor, window, cx| {
27327        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27328    });
27329    cx.assert_editor_state(
27330        "let variable = 42;
27331let another = variable + 1;
27332let result = ˇvariable * 2;",
27333    );
27334
27335    // Now test going backwards from third position
27336    cx.update_editor(|editor, window, cx| {
27337        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27338    });
27339    cx.assert_editor_state(
27340        "let variable = 42;
27341let another = ˇvariable + 1;
27342let result = variable * 2;",
27343    );
27344
27345    // Go to previous highlight - should move to first "variable"
27346    cx.update_editor(|editor, window, cx| {
27347        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27348    });
27349    cx.assert_editor_state(
27350        "let ˇvariable = 42;
27351let another = variable + 1;
27352let result = variable * 2;",
27353    );
27354
27355    // Go to previous highlight - should stay on first "variable"
27356    cx.update_editor(|editor, window, cx| {
27357        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27358    });
27359    cx.assert_editor_state(
27360        "let ˇvariable = 42;
27361let another = variable + 1;
27362let result = variable * 2;",
27363    );
27364}
27365
27366#[gpui::test]
27367async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
27368    cx: &mut gpui::TestAppContext,
27369) {
27370    init_test(cx, |_| {});
27371
27372    let url = "https://zed.dev";
27373
27374    let markdown_language = Arc::new(Language::new(
27375        LanguageConfig {
27376            name: "Markdown".into(),
27377            ..LanguageConfig::default()
27378        },
27379        None,
27380    ));
27381
27382    let mut cx = EditorTestContext::new(cx).await;
27383    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27384    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27385
27386    cx.update_editor(|editor, window, cx| {
27387        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27388        editor.paste(&Paste, window, cx);
27389    });
27390
27391    cx.assert_editor_state(&format!(
27392        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27393    ));
27394}
27395
27396#[gpui::test]
27397async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
27398    cx: &mut gpui::TestAppContext,
27399) {
27400    init_test(cx, |_| {});
27401
27402    let url = "https://zed.dev";
27403
27404    let markdown_language = Arc::new(Language::new(
27405        LanguageConfig {
27406            name: "Markdown".into(),
27407            ..LanguageConfig::default()
27408        },
27409        None,
27410    ));
27411
27412    let mut cx = EditorTestContext::new(cx).await;
27413    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27414    cx.set_state(&format!(
27415        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
27416    ));
27417
27418    cx.update_editor(|editor, window, cx| {
27419        editor.copy(&Copy, window, cx);
27420    });
27421
27422    cx.set_state(&format!(
27423        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
27424    ));
27425
27426    cx.update_editor(|editor, window, cx| {
27427        editor.paste(&Paste, window, cx);
27428    });
27429
27430    cx.assert_editor_state(&format!(
27431        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
27432    ));
27433}
27434
27435#[gpui::test]
27436async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
27437    cx: &mut gpui::TestAppContext,
27438) {
27439    init_test(cx, |_| {});
27440
27441    let url = "https://zed.dev";
27442
27443    let markdown_language = Arc::new(Language::new(
27444        LanguageConfig {
27445            name: "Markdown".into(),
27446            ..LanguageConfig::default()
27447        },
27448        None,
27449    ));
27450
27451    let mut cx = EditorTestContext::new(cx).await;
27452    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27453    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
27454
27455    cx.update_editor(|editor, window, cx| {
27456        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27457        editor.paste(&Paste, window, cx);
27458    });
27459
27460    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
27461}
27462
27463#[gpui::test]
27464async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
27465    cx: &mut gpui::TestAppContext,
27466) {
27467    init_test(cx, |_| {});
27468
27469    let text = "Awesome";
27470
27471    let markdown_language = Arc::new(Language::new(
27472        LanguageConfig {
27473            name: "Markdown".into(),
27474            ..LanguageConfig::default()
27475        },
27476        None,
27477    ));
27478
27479    let mut cx = EditorTestContext::new(cx).await;
27480    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27481    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
27482
27483    cx.update_editor(|editor, window, cx| {
27484        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
27485        editor.paste(&Paste, window, cx);
27486    });
27487
27488    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
27489}
27490
27491#[gpui::test]
27492async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
27493    cx: &mut gpui::TestAppContext,
27494) {
27495    init_test(cx, |_| {});
27496
27497    let url = "https://zed.dev";
27498
27499    let markdown_language = Arc::new(Language::new(
27500        LanguageConfig {
27501            name: "Rust".into(),
27502            ..LanguageConfig::default()
27503        },
27504        None,
27505    ));
27506
27507    let mut cx = EditorTestContext::new(cx).await;
27508    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27509    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
27510
27511    cx.update_editor(|editor, window, cx| {
27512        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27513        editor.paste(&Paste, window, cx);
27514    });
27515
27516    cx.assert_editor_state(&format!(
27517        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
27518    ));
27519}
27520
27521#[gpui::test]
27522async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
27523    cx: &mut TestAppContext,
27524) {
27525    init_test(cx, |_| {});
27526
27527    let url = "https://zed.dev";
27528
27529    let markdown_language = Arc::new(Language::new(
27530        LanguageConfig {
27531            name: "Markdown".into(),
27532            ..LanguageConfig::default()
27533        },
27534        None,
27535    ));
27536
27537    let (editor, cx) = cx.add_window_view(|window, cx| {
27538        let multi_buffer = MultiBuffer::build_multi(
27539            [
27540                ("this will embed -> link", vec![Point::row_range(0..1)]),
27541                ("this will replace -> link", vec![Point::row_range(0..1)]),
27542            ],
27543            cx,
27544        );
27545        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
27546        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27547            s.select_ranges(vec![
27548                Point::new(0, 19)..Point::new(0, 23),
27549                Point::new(1, 21)..Point::new(1, 25),
27550            ])
27551        });
27552        let first_buffer_id = multi_buffer
27553            .read(cx)
27554            .excerpt_buffer_ids()
27555            .into_iter()
27556            .next()
27557            .unwrap();
27558        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
27559        first_buffer.update(cx, |buffer, cx| {
27560            buffer.set_language(Some(markdown_language.clone()), cx);
27561        });
27562
27563        editor
27564    });
27565    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
27566
27567    cx.update_editor(|editor, window, cx| {
27568        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27569        editor.paste(&Paste, window, cx);
27570    });
27571
27572    cx.assert_editor_state(&format!(
27573        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
27574    ));
27575}
27576
27577#[gpui::test]
27578async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
27579    init_test(cx, |_| {});
27580
27581    let fs = FakeFs::new(cx.executor());
27582    fs.insert_tree(
27583        path!("/project"),
27584        json!({
27585            "first.rs": "# First Document\nSome content here.",
27586            "second.rs": "Plain text content for second file.",
27587        }),
27588    )
27589    .await;
27590
27591    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
27592    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27593    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27594
27595    let language = rust_lang();
27596    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27597    language_registry.add(language.clone());
27598    let mut fake_servers = language_registry.register_fake_lsp(
27599        "Rust",
27600        FakeLspAdapter {
27601            ..FakeLspAdapter::default()
27602        },
27603    );
27604
27605    let buffer1 = project
27606        .update(cx, |project, cx| {
27607            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
27608        })
27609        .await
27610        .unwrap();
27611    let buffer2 = project
27612        .update(cx, |project, cx| {
27613            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
27614        })
27615        .await
27616        .unwrap();
27617
27618    let multi_buffer = cx.new(|cx| {
27619        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
27620        multi_buffer.set_excerpts_for_path(
27621            PathKey::for_buffer(&buffer1, cx),
27622            buffer1.clone(),
27623            [Point::zero()..buffer1.read(cx).max_point()],
27624            3,
27625            cx,
27626        );
27627        multi_buffer.set_excerpts_for_path(
27628            PathKey::for_buffer(&buffer2, cx),
27629            buffer2.clone(),
27630            [Point::zero()..buffer1.read(cx).max_point()],
27631            3,
27632            cx,
27633        );
27634        multi_buffer
27635    });
27636
27637    let (editor, cx) = cx.add_window_view(|window, cx| {
27638        Editor::new(
27639            EditorMode::full(),
27640            multi_buffer,
27641            Some(project.clone()),
27642            window,
27643            cx,
27644        )
27645    });
27646
27647    let fake_language_server = fake_servers.next().await.unwrap();
27648
27649    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
27650
27651    let save = editor.update_in(cx, |editor, window, cx| {
27652        assert!(editor.is_dirty(cx));
27653
27654        editor.save(
27655            SaveOptions {
27656                format: true,
27657                autosave: true,
27658            },
27659            project,
27660            window,
27661            cx,
27662        )
27663    });
27664    let (start_edit_tx, start_edit_rx) = oneshot::channel();
27665    let (done_edit_tx, done_edit_rx) = oneshot::channel();
27666    let mut done_edit_rx = Some(done_edit_rx);
27667    let mut start_edit_tx = Some(start_edit_tx);
27668
27669    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
27670        start_edit_tx.take().unwrap().send(()).unwrap();
27671        let done_edit_rx = done_edit_rx.take().unwrap();
27672        async move {
27673            done_edit_rx.await.unwrap();
27674            Ok(None)
27675        }
27676    });
27677
27678    start_edit_rx.await.unwrap();
27679    buffer2
27680        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
27681        .unwrap();
27682
27683    done_edit_tx.send(()).unwrap();
27684
27685    save.await.unwrap();
27686    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
27687}
27688
27689#[track_caller]
27690fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
27691    editor
27692        .all_inlays(cx)
27693        .into_iter()
27694        .filter_map(|inlay| inlay.get_color())
27695        .map(Rgba::from)
27696        .collect()
27697}
27698
27699#[gpui::test]
27700fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
27701    init_test(cx, |_| {});
27702
27703    let editor = cx.add_window(|window, cx| {
27704        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
27705        build_editor(buffer, window, cx)
27706    });
27707
27708    editor
27709        .update(cx, |editor, window, cx| {
27710            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27711                s.select_display_ranges([
27712                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
27713                ])
27714            });
27715
27716            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
27717
27718            assert_eq!(
27719                editor.display_text(cx),
27720                "line1\nline2\nline2",
27721                "Duplicating last line upward should create duplicate above, not on same line"
27722            );
27723
27724            assert_eq!(
27725                editor
27726                    .selections
27727                    .display_ranges(&editor.display_snapshot(cx)),
27728                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
27729                "Selection should move to the duplicated line"
27730            );
27731        })
27732        .unwrap();
27733}
27734
27735#[gpui::test]
27736async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
27737    init_test(cx, |_| {});
27738
27739    let mut cx = EditorTestContext::new(cx).await;
27740
27741    cx.set_state("line1\nline2ˇ");
27742
27743    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27744
27745    let clipboard_text = cx
27746        .read_from_clipboard()
27747        .and_then(|item| item.text().as_deref().map(str::to_string));
27748
27749    assert_eq!(
27750        clipboard_text,
27751        Some("line2\n".to_string()),
27752        "Copying a line without trailing newline should include a newline"
27753    );
27754
27755    cx.set_state("line1\nˇ");
27756
27757    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27758
27759    cx.assert_editor_state("line1\nline2\nˇ");
27760}
27761
27762#[gpui::test]
27763async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27764    init_test(cx, |_| {});
27765
27766    let mut cx = EditorTestContext::new(cx).await;
27767
27768    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27769
27770    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27771
27772    let clipboard_text = cx
27773        .read_from_clipboard()
27774        .and_then(|item| item.text().as_deref().map(str::to_string));
27775
27776    assert_eq!(
27777        clipboard_text,
27778        Some("line1\nline2\nline3\n".to_string()),
27779        "Copying multiple lines should include a single newline between lines"
27780    );
27781
27782    cx.set_state("lineA\nˇ");
27783
27784    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27785
27786    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27787}
27788
27789#[gpui::test]
27790async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27791    init_test(cx, |_| {});
27792
27793    let mut cx = EditorTestContext::new(cx).await;
27794
27795    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27796
27797    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
27798
27799    let clipboard_text = cx
27800        .read_from_clipboard()
27801        .and_then(|item| item.text().as_deref().map(str::to_string));
27802
27803    assert_eq!(
27804        clipboard_text,
27805        Some("line1\nline2\nline3\n".to_string()),
27806        "Copying multiple lines should include a single newline between lines"
27807    );
27808
27809    cx.set_state("lineA\nˇ");
27810
27811    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27812
27813    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27814}
27815
27816#[gpui::test]
27817async fn test_end_of_editor_context(cx: &mut TestAppContext) {
27818    init_test(cx, |_| {});
27819
27820    let mut cx = EditorTestContext::new(cx).await;
27821
27822    cx.set_state("line1\nline2ˇ");
27823    cx.update_editor(|e, window, cx| {
27824        e.set_mode(EditorMode::SingleLine);
27825        assert!(e.key_context(window, cx).contains("end_of_input"));
27826    });
27827    cx.set_state("ˇline1\nline2");
27828    cx.update_editor(|e, window, cx| {
27829        assert!(!e.key_context(window, cx).contains("end_of_input"));
27830    });
27831    cx.set_state("line1ˇ\nline2");
27832    cx.update_editor(|e, window, cx| {
27833        assert!(!e.key_context(window, cx).contains("end_of_input"));
27834    });
27835}
27836
27837#[gpui::test]
27838async fn test_sticky_scroll(cx: &mut TestAppContext) {
27839    init_test(cx, |_| {});
27840    let mut cx = EditorTestContext::new(cx).await;
27841
27842    let buffer = indoc! {"
27843            ˇfn foo() {
27844                let abc = 123;
27845            }
27846            struct Bar;
27847            impl Bar {
27848                fn new() -> Self {
27849                    Self
27850                }
27851            }
27852            fn baz() {
27853            }
27854        "};
27855    cx.set_state(&buffer);
27856
27857    cx.update_editor(|e, _, cx| {
27858        e.buffer()
27859            .read(cx)
27860            .as_singleton()
27861            .unwrap()
27862            .update(cx, |buffer, cx| {
27863                buffer.set_language(Some(rust_lang()), cx);
27864            })
27865    });
27866
27867    let mut sticky_headers = |offset: ScrollOffset| {
27868        cx.update_editor(|e, window, cx| {
27869            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
27870            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), cx)
27871                .into_iter()
27872                .map(
27873                    |StickyHeader {
27874                         start_point,
27875                         offset,
27876                         ..
27877                     }| { (start_point, offset) },
27878                )
27879                .collect::<Vec<_>>()
27880        })
27881    };
27882
27883    let fn_foo = Point { row: 0, column: 0 };
27884    let impl_bar = Point { row: 4, column: 0 };
27885    let fn_new = Point { row: 5, column: 4 };
27886
27887    assert_eq!(sticky_headers(0.0), vec![]);
27888    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
27889    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
27890    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
27891    assert_eq!(sticky_headers(2.0), vec![]);
27892    assert_eq!(sticky_headers(2.5), vec![]);
27893    assert_eq!(sticky_headers(3.0), vec![]);
27894    assert_eq!(sticky_headers(3.5), vec![]);
27895    assert_eq!(sticky_headers(4.0), vec![]);
27896    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27897    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27898    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
27899    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
27900    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
27901    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
27902    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
27903    assert_eq!(sticky_headers(8.0), vec![]);
27904    assert_eq!(sticky_headers(8.5), vec![]);
27905    assert_eq!(sticky_headers(9.0), vec![]);
27906    assert_eq!(sticky_headers(9.5), vec![]);
27907    assert_eq!(sticky_headers(10.0), vec![]);
27908}
27909
27910#[gpui::test]
27911async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
27912    init_test(cx, |_| {});
27913    cx.update(|cx| {
27914        SettingsStore::update_global(cx, |store, cx| {
27915            store.update_user_settings(cx, |settings| {
27916                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
27917                    enabled: Some(true),
27918                })
27919            });
27920        });
27921    });
27922    let mut cx = EditorTestContext::new(cx).await;
27923
27924    let line_height = cx.editor(|editor, window, _cx| {
27925        editor
27926            .style()
27927            .unwrap()
27928            .text
27929            .line_height_in_pixels(window.rem_size())
27930    });
27931
27932    let buffer = indoc! {"
27933            ˇfn foo() {
27934                let abc = 123;
27935            }
27936            struct Bar;
27937            impl Bar {
27938                fn new() -> Self {
27939                    Self
27940                }
27941            }
27942            fn baz() {
27943            }
27944        "};
27945    cx.set_state(&buffer);
27946
27947    cx.update_editor(|e, _, cx| {
27948        e.buffer()
27949            .read(cx)
27950            .as_singleton()
27951            .unwrap()
27952            .update(cx, |buffer, cx| {
27953                buffer.set_language(Some(rust_lang()), cx);
27954            })
27955    });
27956
27957    let fn_foo = || empty_range(0, 0);
27958    let impl_bar = || empty_range(4, 0);
27959    let fn_new = || empty_range(5, 4);
27960
27961    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
27962        cx.update_editor(|e, window, cx| {
27963            e.scroll(
27964                gpui::Point {
27965                    x: 0.,
27966                    y: scroll_offset,
27967                },
27968                None,
27969                window,
27970                cx,
27971            );
27972        });
27973        cx.simulate_click(
27974            gpui::Point {
27975                x: px(0.),
27976                y: click_offset as f32 * line_height,
27977            },
27978            Modifiers::none(),
27979        );
27980        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
27981    };
27982
27983    assert_eq!(
27984        scroll_and_click(
27985            4.5, // impl Bar is halfway off the screen
27986            0.0  // click top of screen
27987        ),
27988        // scrolled to impl Bar
27989        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27990    );
27991
27992    assert_eq!(
27993        scroll_and_click(
27994            4.5,  // impl Bar is halfway off the screen
27995            0.25  // click middle of impl Bar
27996        ),
27997        // scrolled to impl Bar
27998        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27999    );
28000
28001    assert_eq!(
28002        scroll_and_click(
28003            4.5, // impl Bar is halfway off the screen
28004            1.5  // click below impl Bar (e.g. fn new())
28005        ),
28006        // scrolled to fn new() - this is below the impl Bar header which has persisted
28007        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28008    );
28009
28010    assert_eq!(
28011        scroll_and_click(
28012            5.5,  // fn new is halfway underneath impl Bar
28013            0.75  // click on the overlap of impl Bar and fn new()
28014        ),
28015        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28016    );
28017
28018    assert_eq!(
28019        scroll_and_click(
28020            5.5,  // fn new is halfway underneath impl Bar
28021            1.25  // click on the visible part of fn new()
28022        ),
28023        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28024    );
28025
28026    assert_eq!(
28027        scroll_and_click(
28028            1.5, // fn foo is halfway off the screen
28029            0.0  // click top of screen
28030        ),
28031        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
28032    );
28033
28034    assert_eq!(
28035        scroll_and_click(
28036            1.5,  // fn foo is halfway off the screen
28037            0.75  // click visible part of let abc...
28038        )
28039        .0,
28040        // no change in scroll
28041        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
28042        (gpui::Point { x: 0., y: 1.5 })
28043    );
28044}
28045
28046#[gpui::test]
28047async fn test_next_prev_reference(cx: &mut TestAppContext) {
28048    const CYCLE_POSITIONS: &[&'static str] = &[
28049        indoc! {"
28050            fn foo() {
28051                let ˇabc = 123;
28052                let x = abc + 1;
28053                let y = abc + 2;
28054                let z = abc + 2;
28055            }
28056        "},
28057        indoc! {"
28058            fn foo() {
28059                let abc = 123;
28060                let x = ˇabc + 1;
28061                let y = abc + 2;
28062                let z = abc + 2;
28063            }
28064        "},
28065        indoc! {"
28066            fn foo() {
28067                let abc = 123;
28068                let x = abc + 1;
28069                let y = ˇabc + 2;
28070                let z = abc + 2;
28071            }
28072        "},
28073        indoc! {"
28074            fn foo() {
28075                let abc = 123;
28076                let x = abc + 1;
28077                let y = abc + 2;
28078                let z = ˇabc + 2;
28079            }
28080        "},
28081    ];
28082
28083    init_test(cx, |_| {});
28084
28085    let mut cx = EditorLspTestContext::new_rust(
28086        lsp::ServerCapabilities {
28087            references_provider: Some(lsp::OneOf::Left(true)),
28088            ..Default::default()
28089        },
28090        cx,
28091    )
28092    .await;
28093
28094    // importantly, the cursor is in the middle
28095    cx.set_state(indoc! {"
28096        fn foo() {
28097            let aˇbc = 123;
28098            let x = abc + 1;
28099            let y = abc + 2;
28100            let z = abc + 2;
28101        }
28102    "});
28103
28104    let reference_ranges = [
28105        lsp::Position::new(1, 8),
28106        lsp::Position::new(2, 12),
28107        lsp::Position::new(3, 12),
28108        lsp::Position::new(4, 12),
28109    ]
28110    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
28111
28112    cx.lsp
28113        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
28114            Ok(Some(
28115                reference_ranges
28116                    .map(|range| lsp::Location {
28117                        uri: params.text_document_position.text_document.uri.clone(),
28118                        range,
28119                    })
28120                    .to_vec(),
28121            ))
28122        });
28123
28124    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
28125        cx.update_editor(|editor, window, cx| {
28126            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
28127        })
28128        .unwrap()
28129        .await
28130        .unwrap()
28131    };
28132
28133    _move(Direction::Next, 1, &mut cx).await;
28134    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28135
28136    _move(Direction::Next, 1, &mut cx).await;
28137    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28138
28139    _move(Direction::Next, 1, &mut cx).await;
28140    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28141
28142    // loops back to the start
28143    _move(Direction::Next, 1, &mut cx).await;
28144    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28145
28146    // loops back to the end
28147    _move(Direction::Prev, 1, &mut cx).await;
28148    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28149
28150    _move(Direction::Prev, 1, &mut cx).await;
28151    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28152
28153    _move(Direction::Prev, 1, &mut cx).await;
28154    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28155
28156    _move(Direction::Prev, 1, &mut cx).await;
28157    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28158
28159    _move(Direction::Next, 3, &mut cx).await;
28160    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28161
28162    _move(Direction::Prev, 2, &mut cx).await;
28163    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28164}
28165
28166#[gpui::test]
28167async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
28168    init_test(cx, |_| {});
28169
28170    let (editor, cx) = cx.add_window_view(|window, cx| {
28171        let multi_buffer = MultiBuffer::build_multi(
28172            [
28173                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28174                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28175            ],
28176            cx,
28177        );
28178        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28179    });
28180
28181    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28182    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
28183
28184    cx.assert_excerpts_with_selections(indoc! {"
28185        [EXCERPT]
28186        ˇ1
28187        2
28188        3
28189        [EXCERPT]
28190        1
28191        2
28192        3
28193        "});
28194
28195    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
28196    cx.update_editor(|editor, window, cx| {
28197        editor.change_selections(None.into(), window, cx, |s| {
28198            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28199        });
28200    });
28201    cx.assert_excerpts_with_selections(indoc! {"
28202        [EXCERPT]
28203        1
2820428205        3
28206        [EXCERPT]
28207        1
28208        2
28209        3
28210        "});
28211
28212    cx.update_editor(|editor, window, cx| {
28213        editor
28214            .select_all_matches(&SelectAllMatches, window, cx)
28215            .unwrap();
28216    });
28217    cx.assert_excerpts_with_selections(indoc! {"
28218        [EXCERPT]
28219        1
2822028221        3
28222        [EXCERPT]
28223        1
2822428225        3
28226        "});
28227
28228    cx.update_editor(|editor, window, cx| {
28229        editor.handle_input("X", window, cx);
28230    });
28231    cx.assert_excerpts_with_selections(indoc! {"
28232        [EXCERPT]
28233        1
2823428235        3
28236        [EXCERPT]
28237        1
2823828239        3
28240        "});
28241
28242    // Scenario 2: Select "2", then fold second buffer before insertion
28243    cx.update_multibuffer(|mb, cx| {
28244        for buffer_id in buffer_ids.iter() {
28245            let buffer = mb.buffer(*buffer_id).unwrap();
28246            buffer.update(cx, |buffer, cx| {
28247                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28248            });
28249        }
28250    });
28251
28252    // Select "2" and select all matches
28253    cx.update_editor(|editor, window, cx| {
28254        editor.change_selections(None.into(), window, cx, |s| {
28255            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28256        });
28257        editor
28258            .select_all_matches(&SelectAllMatches, window, cx)
28259            .unwrap();
28260    });
28261
28262    // Fold second buffer - should remove selections from folded buffer
28263    cx.update_editor(|editor, _, cx| {
28264        editor.fold_buffer(buffer_ids[1], cx);
28265    });
28266    cx.assert_excerpts_with_selections(indoc! {"
28267        [EXCERPT]
28268        1
2826928270        3
28271        [EXCERPT]
28272        [FOLDED]
28273        "});
28274
28275    // Insert text - should only affect first buffer
28276    cx.update_editor(|editor, window, cx| {
28277        editor.handle_input("Y", window, cx);
28278    });
28279    cx.update_editor(|editor, _, cx| {
28280        editor.unfold_buffer(buffer_ids[1], cx);
28281    });
28282    cx.assert_excerpts_with_selections(indoc! {"
28283        [EXCERPT]
28284        1
2828528286        3
28287        [EXCERPT]
28288        1
28289        2
28290        3
28291        "});
28292
28293    // Scenario 3: Select "2", then fold first buffer before insertion
28294    cx.update_multibuffer(|mb, cx| {
28295        for buffer_id in buffer_ids.iter() {
28296            let buffer = mb.buffer(*buffer_id).unwrap();
28297            buffer.update(cx, |buffer, cx| {
28298                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28299            });
28300        }
28301    });
28302
28303    // Select "2" and select all matches
28304    cx.update_editor(|editor, window, cx| {
28305        editor.change_selections(None.into(), window, cx, |s| {
28306            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28307        });
28308        editor
28309            .select_all_matches(&SelectAllMatches, window, cx)
28310            .unwrap();
28311    });
28312
28313    // Fold first buffer - should remove selections from folded buffer
28314    cx.update_editor(|editor, _, cx| {
28315        editor.fold_buffer(buffer_ids[0], cx);
28316    });
28317    cx.assert_excerpts_with_selections(indoc! {"
28318        [EXCERPT]
28319        [FOLDED]
28320        [EXCERPT]
28321        1
2832228323        3
28324        "});
28325
28326    // Insert text - should only affect second buffer
28327    cx.update_editor(|editor, window, cx| {
28328        editor.handle_input("Z", window, cx);
28329    });
28330    cx.update_editor(|editor, _, cx| {
28331        editor.unfold_buffer(buffer_ids[0], cx);
28332    });
28333    cx.assert_excerpts_with_selections(indoc! {"
28334        [EXCERPT]
28335        1
28336        2
28337        3
28338        [EXCERPT]
28339        1
2834028341        3
28342        "});
28343
28344    // Edge case scenario: fold all buffers, then try to insert
28345    cx.update_editor(|editor, _, cx| {
28346        editor.fold_buffer(buffer_ids[0], cx);
28347        editor.fold_buffer(buffer_ids[1], cx);
28348    });
28349    cx.assert_excerpts_with_selections(indoc! {"
28350        [EXCERPT]
28351        ˇ[FOLDED]
28352        [EXCERPT]
28353        [FOLDED]
28354        "});
28355
28356    // Insert should work via default selection
28357    cx.update_editor(|editor, window, cx| {
28358        editor.handle_input("W", window, cx);
28359    });
28360    cx.update_editor(|editor, _, cx| {
28361        editor.unfold_buffer(buffer_ids[0], cx);
28362        editor.unfold_buffer(buffer_ids[1], cx);
28363    });
28364    cx.assert_excerpts_with_selections(indoc! {"
28365        [EXCERPT]
28366        Wˇ1
28367        2
28368        3
28369        [EXCERPT]
28370        1
28371        Z
28372        3
28373        "});
28374}
28375
28376// FIXME restore these tests in some form
28377// #[gpui::test]
28378// async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
28379//     init_test(cx, |_| {});
28380//     let mut leader_cx = EditorTestContext::new(cx).await;
28381
28382//     let diff_base = indoc!(
28383//         r#"
28384//         one
28385//         two
28386//         three
28387//         four
28388//         five
28389//         six
28390//         "#
28391//     );
28392
28393//     let initial_state = indoc!(
28394//         r#"
28395//         ˇone
28396//         two
28397//         THREE
28398//         four
28399//         five
28400//         six
28401//         "#
28402//     );
28403
28404//     leader_cx.set_state(initial_state);
28405
28406//     leader_cx.set_head_text(&diff_base);
28407//     leader_cx.run_until_parked();
28408
28409//     let follower = leader_cx.update_multibuffer(|leader, cx| {
28410//         leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
28411//         leader.set_all_diff_hunks_expanded(cx);
28412//         leader.get_or_create_follower(cx)
28413//     });
28414//     follower.update(cx, |follower, cx| {
28415//         follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
28416//         follower.set_all_diff_hunks_expanded(cx);
28417//     });
28418
28419//     let follower_editor =
28420//         leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
28421//     // leader_cx.window.focus(&follower_editor.focus_handle(cx));
28422
28423//     let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
28424//     cx.run_until_parked();
28425
28426//     leader_cx.assert_editor_state(initial_state);
28427//     follower_cx.assert_editor_state(indoc! {
28428//         r#"
28429//         ˇone
28430//         two
28431//         three
28432//         four
28433//         five
28434//         six
28435//         "#
28436//     });
28437
28438//     follower_cx.editor(|editor, _window, cx| {
28439//         assert!(editor.read_only(cx));
28440//     });
28441
28442//     leader_cx.update_editor(|editor, _window, cx| {
28443//         editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
28444//     });
28445//     cx.run_until_parked();
28446
28447//     leader_cx.assert_editor_state(indoc! {
28448//         r#"
28449//         ˇone
28450//         two
28451//         THREE
28452//         four
28453//         FIVE
28454//         six
28455//         "#
28456//     });
28457
28458//     follower_cx.assert_editor_state(indoc! {
28459//         r#"
28460//         ˇone
28461//         two
28462//         three
28463//         four
28464//         five
28465//         six
28466//         "#
28467//     });
28468
28469//     leader_cx.update_editor(|editor, _window, cx| {
28470//         editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
28471//     });
28472//     cx.run_until_parked();
28473
28474//     leader_cx.assert_editor_state(indoc! {
28475//         r#"
28476//         ˇone
28477//         two
28478//         THREE
28479//         four
28480//         FIVE
28481//         six
28482//         SEVEN"#
28483//     });
28484
28485//     follower_cx.assert_editor_state(indoc! {
28486//         r#"
28487//         ˇone
28488//         two
28489//         three
28490//         four
28491//         five
28492//         six
28493//         "#
28494//     });
28495
28496//     leader_cx.update_editor(|editor, window, cx| {
28497//         editor.move_down(&MoveDown, window, cx);
28498//         editor.refresh_selected_text_highlights(true, window, cx);
28499//     });
28500//     leader_cx.run_until_parked();
28501// }
28502
28503// #[gpui::test]
28504// async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
28505//     init_test(cx, |_| {});
28506//     let base_text = "base\n";
28507//     let buffer_text = "buffer\n";
28508
28509//     let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
28510//     let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
28511
28512//     let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
28513//     let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
28514//     let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
28515//     let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
28516
28517//     let leader = cx.new(|cx| {
28518//         let mut leader = MultiBuffer::new(Capability::ReadWrite);
28519//         leader.set_all_diff_hunks_expanded(cx);
28520//         leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
28521//         leader
28522//     });
28523//     let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
28524//     follower.update(cx, |follower, _| {
28525//         follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
28526//     });
28527
28528//     leader.update(cx, |leader, cx| {
28529//         leader.insert_excerpts_after(
28530//             ExcerptId::min(),
28531//             extra_buffer_2.clone(),
28532//             vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28533//             cx,
28534//         );
28535//         leader.add_diff(extra_diff_2.clone(), cx);
28536
28537//         leader.insert_excerpts_after(
28538//             ExcerptId::min(),
28539//             extra_buffer_1.clone(),
28540//             vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28541//             cx,
28542//         );
28543//         leader.add_diff(extra_diff_1.clone(), cx);
28544
28545//         leader.insert_excerpts_after(
28546//             ExcerptId::min(),
28547//             buffer1.clone(),
28548//             vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28549//             cx,
28550//         );
28551//         leader.add_diff(diff1.clone(), cx);
28552//     });
28553
28554//     cx.run_until_parked();
28555//     let mut cx = cx.add_empty_window();
28556
28557//     let leader_editor = cx
28558//         .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
28559//     let follower_editor = cx.new_window_entity(|window, cx| {
28560//         Editor::for_multibuffer(follower.clone(), None, window, cx)
28561//     });
28562
28563//     let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
28564//     leader_cx.assert_editor_state(indoc! {"
28565//        ˇbuffer
28566
28567//        dummy text 1
28568
28569//        dummy text 2
28570//     "});
28571//     let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
28572//     follower_cx.assert_editor_state(indoc! {"
28573//         ˇbase
28574
28575//     "});
28576// }
28577
28578#[gpui::test]
28579async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
28580    init_test(cx, |_| {});
28581
28582    let (editor, cx) = cx.add_window_view(|window, cx| {
28583        let multi_buffer = MultiBuffer::build_multi(
28584            [
28585                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28586                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
28587            ],
28588            cx,
28589        );
28590        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28591    });
28592
28593    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28594
28595    cx.assert_excerpts_with_selections(indoc! {"
28596        [EXCERPT]
28597        ˇ1
28598        2
28599        3
28600        [EXCERPT]
28601        1
28602        2
28603        3
28604        4
28605        5
28606        6
28607        7
28608        8
28609        9
28610        "});
28611
28612    cx.update_editor(|editor, window, cx| {
28613        editor.change_selections(None.into(), window, cx, |s| {
28614            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
28615        });
28616    });
28617
28618    cx.assert_excerpts_with_selections(indoc! {"
28619        [EXCERPT]
28620        1
28621        2
28622        3
28623        [EXCERPT]
28624        1
28625        2
28626        3
28627        4
28628        5
28629        6
28630        ˇ7
28631        8
28632        9
28633        "});
28634
28635    cx.update_editor(|editor, _window, cx| {
28636        editor.set_vertical_scroll_margin(0, cx);
28637    });
28638
28639    cx.update_editor(|editor, window, cx| {
28640        assert_eq!(editor.vertical_scroll_margin(), 0);
28641        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
28642        assert_eq!(
28643            editor.snapshot(window, cx).scroll_position(),
28644            gpui::Point::new(0., 12.0)
28645        );
28646    });
28647
28648    cx.update_editor(|editor, _window, cx| {
28649        editor.set_vertical_scroll_margin(3, cx);
28650    });
28651
28652    cx.update_editor(|editor, window, cx| {
28653        assert_eq!(editor.vertical_scroll_margin(), 3);
28654        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
28655        assert_eq!(
28656            editor.snapshot(window, cx).scroll_position(),
28657            gpui::Point::new(0., 9.0)
28658        );
28659    });
28660}