editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionDelegate,
    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, 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, DEFAULT_LSP_REQUEST_TIMEOUT};
   38use multi_buffer::{
   39    ExcerptRange, IndentGuide, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
   40};
   41use parking_lot::Mutex;
   42use pretty_assertions::{assert_eq, assert_ne};
   43use project::{
   44    FakeFs, Project,
   45    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   46    project_settings::LspSettings,
   47    trusted_worktrees::{PathTrust, TrustedWorktrees},
   48};
   49use serde_json::{self, json};
   50use settings::{
   51    AllLanguageSettingsContent, DelayMs, EditorSettingsContent, GlobalLspSettingsContent,
   52    IndentGuideBackgroundColoring, IndentGuideColoring, InlayHintSettingsContent,
   53    ProjectSettingsContent, SearchSettingsContent, SettingsStore,
   54};
   55use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   56use std::{
   57    iter,
   58    sync::atomic::{self, AtomicUsize},
   59};
   60use test::build_editor_with_project;
   61use text::ToPoint as _;
   62use unindent::Unindent;
   63use util::{
   64    assert_set_eq, path,
   65    rel_path::rel_path,
   66    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   67    uri,
   68};
   69use workspace::{
   70    CloseActiveItem, CloseAllItems, CloseOtherItems, NavigationEntry, OpenOptions, ViewId,
   71    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   72    register_project_item,
   73};
   74
   75fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   76    editor
   77        .selections
   78        .display_ranges(&editor.display_snapshot(cx))
   79}
   80
   81#[gpui::test]
   82fn test_edit_events(cx: &mut TestAppContext) {
   83    init_test(cx, |_| {});
   84
   85    let buffer = cx.new(|cx| {
   86        let mut buffer = language::Buffer::local("123456", cx);
   87        buffer.set_group_interval(Duration::from_secs(1));
   88        buffer
   89    });
   90
   91    let events = Rc::new(RefCell::new(Vec::new()));
   92    let editor1 = cx.add_window({
   93        let events = events.clone();
   94        |window, cx| {
   95            let entity = cx.entity();
   96            cx.subscribe_in(
   97                &entity,
   98                window,
   99                move |_, _, event: &EditorEvent, _, _| match event {
  100                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
  101                    EditorEvent::BufferEdited => {
  102                        events.borrow_mut().push(("editor1", "buffer edited"))
  103                    }
  104                    _ => {}
  105                },
  106            )
  107            .detach();
  108            Editor::for_buffer(buffer.clone(), None, window, cx)
  109        }
  110    });
  111
  112    let editor2 = cx.add_window({
  113        let events = events.clone();
  114        |window, cx| {
  115            cx.subscribe_in(
  116                &cx.entity(),
  117                window,
  118                move |_, _, event: &EditorEvent, _, _| match event {
  119                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  120                    EditorEvent::BufferEdited => {
  121                        events.borrow_mut().push(("editor2", "buffer edited"))
  122                    }
  123                    _ => {}
  124                },
  125            )
  126            .detach();
  127            Editor::for_buffer(buffer.clone(), None, window, cx)
  128        }
  129    });
  130
  131    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  132
  133    // Mutating editor 1 will emit an `Edited` event only for that editor.
  134    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  135    assert_eq!(
  136        mem::take(&mut *events.borrow_mut()),
  137        [
  138            ("editor1", "edited"),
  139            ("editor1", "buffer edited"),
  140            ("editor2", "buffer edited"),
  141        ]
  142    );
  143
  144    // Mutating editor 2 will emit an `Edited` event only for that editor.
  145    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  146    assert_eq!(
  147        mem::take(&mut *events.borrow_mut()),
  148        [
  149            ("editor2", "edited"),
  150            ("editor1", "buffer edited"),
  151            ("editor2", "buffer edited"),
  152        ]
  153    );
  154
  155    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  156    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  157    assert_eq!(
  158        mem::take(&mut *events.borrow_mut()),
  159        [
  160            ("editor1", "edited"),
  161            ("editor1", "buffer edited"),
  162            ("editor2", "buffer edited"),
  163        ]
  164    );
  165
  166    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  167    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  168    assert_eq!(
  169        mem::take(&mut *events.borrow_mut()),
  170        [
  171            ("editor1", "edited"),
  172            ("editor1", "buffer edited"),
  173            ("editor2", "buffer edited"),
  174        ]
  175    );
  176
  177    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  178    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  179    assert_eq!(
  180        mem::take(&mut *events.borrow_mut()),
  181        [
  182            ("editor2", "edited"),
  183            ("editor1", "buffer edited"),
  184            ("editor2", "buffer edited"),
  185        ]
  186    );
  187
  188    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  189    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  190    assert_eq!(
  191        mem::take(&mut *events.borrow_mut()),
  192        [
  193            ("editor2", "edited"),
  194            ("editor1", "buffer edited"),
  195            ("editor2", "buffer edited"),
  196        ]
  197    );
  198
  199    // No event is emitted when the mutation is a no-op.
  200    _ = editor2.update(cx, |editor, window, cx| {
  201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  202            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  203        });
  204
  205        editor.backspace(&Backspace, window, cx);
  206    });
  207    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  208}
  209
  210#[gpui::test]
  211fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  212    init_test(cx, |_| {});
  213
  214    let mut now = Instant::now();
  215    let group_interval = Duration::from_millis(1);
  216    let buffer = cx.new(|cx| {
  217        let mut buf = language::Buffer::local("123456", cx);
  218        buf.set_group_interval(group_interval);
  219        buf
  220    });
  221    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  222    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  223
  224    _ = editor.update(cx, |editor, window, cx| {
  225        editor.start_transaction_at(now, window, cx);
  226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  227            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  228        });
  229
  230        editor.insert("cd", window, cx);
  231        editor.end_transaction_at(now, cx);
  232        assert_eq!(editor.text(cx), "12cd56");
  233        assert_eq!(
  234            editor.selections.ranges(&editor.display_snapshot(cx)),
  235            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  236        );
  237
  238        editor.start_transaction_at(now, window, cx);
  239        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  240            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  241        });
  242        editor.insert("e", window, cx);
  243        editor.end_transaction_at(now, cx);
  244        assert_eq!(editor.text(cx), "12cde6");
  245        assert_eq!(
  246            editor.selections.ranges(&editor.display_snapshot(cx)),
  247            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  248        );
  249
  250        now += group_interval + Duration::from_millis(1);
  251        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  252            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  253        });
  254
  255        // Simulate an edit in another editor
  256        buffer.update(cx, |buffer, cx| {
  257            buffer.start_transaction_at(now, cx);
  258            buffer.edit(
  259                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  260                None,
  261                cx,
  262            );
  263            buffer.edit(
  264                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  265                None,
  266                cx,
  267            );
  268            buffer.end_transaction_at(now, cx);
  269        });
  270
  271        assert_eq!(editor.text(cx), "ab2cde6");
  272        assert_eq!(
  273            editor.selections.ranges(&editor.display_snapshot(cx)),
  274            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  275        );
  276
  277        // Last transaction happened past the group interval in a different editor.
  278        // Undo it individually and don't restore selections.
  279        editor.undo(&Undo, window, cx);
  280        assert_eq!(editor.text(cx), "12cde6");
  281        assert_eq!(
  282            editor.selections.ranges(&editor.display_snapshot(cx)),
  283            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  284        );
  285
  286        // First two transactions happened within the group interval in this editor.
  287        // Undo them together and restore selections.
  288        editor.undo(&Undo, window, cx);
  289        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  290        assert_eq!(editor.text(cx), "123456");
  291        assert_eq!(
  292            editor.selections.ranges(&editor.display_snapshot(cx)),
  293            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  294        );
  295
  296        // Redo the first two transactions together.
  297        editor.redo(&Redo, window, cx);
  298        assert_eq!(editor.text(cx), "12cde6");
  299        assert_eq!(
  300            editor.selections.ranges(&editor.display_snapshot(cx)),
  301            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  302        );
  303
  304        // Redo the last transaction on its own.
  305        editor.redo(&Redo, window, cx);
  306        assert_eq!(editor.text(cx), "ab2cde6");
  307        assert_eq!(
  308            editor.selections.ranges(&editor.display_snapshot(cx)),
  309            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  310        );
  311
  312        // Test empty transactions.
  313        editor.start_transaction_at(now, window, cx);
  314        editor.end_transaction_at(now, cx);
  315        editor.undo(&Undo, window, cx);
  316        assert_eq!(editor.text(cx), "12cde6");
  317    });
  318}
  319
  320#[gpui::test]
  321fn test_ime_composition(cx: &mut TestAppContext) {
  322    init_test(cx, |_| {});
  323
  324    let buffer = cx.new(|cx| {
  325        let mut buffer = language::Buffer::local("abcde", cx);
  326        // Ensure automatic grouping doesn't occur.
  327        buffer.set_group_interval(Duration::ZERO);
  328        buffer
  329    });
  330
  331    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  332    cx.add_window(|window, cx| {
  333        let mut editor = build_editor(buffer.clone(), window, cx);
  334
  335        // Start a new IME composition.
  336        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  337        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  338        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  339        assert_eq!(editor.text(cx), "äbcde");
  340        assert_eq!(
  341            editor.marked_text_ranges(cx),
  342            Some(vec![
  343                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  344            ])
  345        );
  346
  347        // Finalize IME composition.
  348        editor.replace_text_in_range(None, "ā", window, cx);
  349        assert_eq!(editor.text(cx), "ābcde");
  350        assert_eq!(editor.marked_text_ranges(cx), None);
  351
  352        // IME composition edits are grouped and are undone/redone at once.
  353        editor.undo(&Default::default(), window, cx);
  354        assert_eq!(editor.text(cx), "abcde");
  355        assert_eq!(editor.marked_text_ranges(cx), None);
  356        editor.redo(&Default::default(), window, cx);
  357        assert_eq!(editor.text(cx), "ābcde");
  358        assert_eq!(editor.marked_text_ranges(cx), None);
  359
  360        // Start a new IME composition.
  361        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  362        assert_eq!(
  363            editor.marked_text_ranges(cx),
  364            Some(vec![
  365                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  366            ])
  367        );
  368
  369        // Undoing during an IME composition cancels it.
  370        editor.undo(&Default::default(), window, cx);
  371        assert_eq!(editor.text(cx), "ābcde");
  372        assert_eq!(editor.marked_text_ranges(cx), None);
  373
  374        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  375        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  376        assert_eq!(editor.text(cx), "ābcdè");
  377        assert_eq!(
  378            editor.marked_text_ranges(cx),
  379            Some(vec![
  380                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  381            ])
  382        );
  383
  384        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  385        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  386        assert_eq!(editor.text(cx), "ābcdę");
  387        assert_eq!(editor.marked_text_ranges(cx), None);
  388
  389        // Start a new IME composition with multiple cursors.
  390        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  391            s.select_ranges([
  392                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  393                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  394                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  395            ])
  396        });
  397        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  398        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  399        assert_eq!(
  400            editor.marked_text_ranges(cx),
  401            Some(vec![
  402                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  403                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  404                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  405            ])
  406        );
  407
  408        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  409        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  410        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  411        assert_eq!(
  412            editor.marked_text_ranges(cx),
  413            Some(vec![
  414                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  415                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  416                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  417            ])
  418        );
  419
  420        // Finalize IME composition with multiple cursors.
  421        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  422        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  423        assert_eq!(editor.marked_text_ranges(cx), None);
  424
  425        editor
  426    });
  427}
  428
  429#[gpui::test]
  430fn test_selection_with_mouse(cx: &mut TestAppContext) {
  431    init_test(cx, |_| {});
  432
  433    let editor = cx.add_window(|window, cx| {
  434        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  435        build_editor(buffer, window, cx)
  436    });
  437
  438    _ = editor.update(cx, |editor, window, cx| {
  439        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  440    });
  441    assert_eq!(
  442        editor
  443            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  444            .unwrap(),
  445        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  446    );
  447
  448    _ = editor.update(cx, |editor, window, cx| {
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(3), 3),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  461            .unwrap(),
  462        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  463    );
  464
  465    _ = editor.update(cx, |editor, window, cx| {
  466        editor.update_selection(
  467            DisplayPoint::new(DisplayRow(1), 1),
  468            0,
  469            gpui::Point::<f32>::default(),
  470            window,
  471            cx,
  472        );
  473    });
  474
  475    assert_eq!(
  476        editor
  477            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  478            .unwrap(),
  479        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  480    );
  481
  482    _ = editor.update(cx, |editor, window, cx| {
  483        editor.end_selection(window, cx);
  484        editor.update_selection(
  485            DisplayPoint::new(DisplayRow(3), 3),
  486            0,
  487            gpui::Point::<f32>::default(),
  488            window,
  489            cx,
  490        );
  491    });
  492
  493    assert_eq!(
  494        editor
  495            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  496            .unwrap(),
  497        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  498    );
  499
  500    _ = editor.update(cx, |editor, window, cx| {
  501        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  502        editor.update_selection(
  503            DisplayPoint::new(DisplayRow(0), 0),
  504            0,
  505            gpui::Point::<f32>::default(),
  506            window,
  507            cx,
  508        );
  509    });
  510
  511    assert_eq!(
  512        editor
  513            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  514            .unwrap(),
  515        [
  516            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  517            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  518        ]
  519    );
  520
  521    _ = editor.update(cx, |editor, window, cx| {
  522        editor.end_selection(window, cx);
  523    });
  524
  525    assert_eq!(
  526        editor
  527            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  528            .unwrap(),
  529        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  530    );
  531}
  532
  533#[gpui::test]
  534fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  535    init_test(cx, |_| {});
  536
  537    let editor = cx.add_window(|window, cx| {
  538        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  539        build_editor(buffer, window, cx)
  540    });
  541
  542    _ = editor.update(cx, |editor, window, cx| {
  543        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  544    });
  545
  546    _ = editor.update(cx, |editor, window, cx| {
  547        editor.end_selection(window, cx);
  548    });
  549
  550    _ = editor.update(cx, |editor, window, cx| {
  551        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  552    });
  553
  554    _ = editor.update(cx, |editor, window, cx| {
  555        editor.end_selection(window, cx);
  556    });
  557
  558    assert_eq!(
  559        editor
  560            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  561            .unwrap(),
  562        [
  563            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  564            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  565        ]
  566    );
  567
  568    _ = editor.update(cx, |editor, window, cx| {
  569        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  570    });
  571
  572    _ = editor.update(cx, |editor, window, cx| {
  573        editor.end_selection(window, cx);
  574    });
  575
  576    assert_eq!(
  577        editor
  578            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  579            .unwrap(),
  580        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  581    );
  582}
  583
  584#[gpui::test]
  585fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  586    init_test(cx, |_| {});
  587
  588    let editor = cx.add_window(|window, cx| {
  589        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  590        build_editor(buffer, window, cx)
  591    });
  592
  593    _ = editor.update(cx, |editor, window, cx| {
  594        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  595        assert_eq!(
  596            display_ranges(editor, cx),
  597            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  598        );
  599    });
  600
  601    _ = editor.update(cx, |editor, window, cx| {
  602        editor.update_selection(
  603            DisplayPoint::new(DisplayRow(3), 3),
  604            0,
  605            gpui::Point::<f32>::default(),
  606            window,
  607            cx,
  608        );
  609        assert_eq!(
  610            display_ranges(editor, cx),
  611            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  612        );
  613    });
  614
  615    _ = editor.update(cx, |editor, window, cx| {
  616        editor.cancel(&Cancel, window, cx);
  617        editor.update_selection(
  618            DisplayPoint::new(DisplayRow(1), 1),
  619            0,
  620            gpui::Point::<f32>::default(),
  621            window,
  622            cx,
  623        );
  624        assert_eq!(
  625            display_ranges(editor, cx),
  626            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  627        );
  628    });
  629}
  630
  631#[gpui::test]
  632fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  633    init_test(cx, |_| {});
  634
  635    let editor = cx.add_window(|window, cx| {
  636        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  637        build_editor(buffer, window, cx)
  638    });
  639
  640    _ = editor.update(cx, |editor, window, cx| {
  641        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  642        assert_eq!(
  643            display_ranges(editor, cx),
  644            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  645        );
  646
  647        editor.move_down(&Default::default(), window, cx);
  648        assert_eq!(
  649            display_ranges(editor, cx),
  650            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  651        );
  652
  653        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  654        assert_eq!(
  655            display_ranges(editor, cx),
  656            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  657        );
  658
  659        editor.move_up(&Default::default(), window, cx);
  660        assert_eq!(
  661            display_ranges(editor, cx),
  662            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  663        );
  664    });
  665}
  666
  667#[gpui::test]
  668fn test_extending_selection(cx: &mut TestAppContext) {
  669    init_test(cx, |_| {});
  670
  671    let editor = cx.add_window(|window, cx| {
  672        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  673        build_editor(buffer, window, cx)
  674    });
  675
  676    _ = editor.update(cx, |editor, window, cx| {
  677        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  678        editor.end_selection(window, cx);
  679        assert_eq!(
  680            display_ranges(editor, cx),
  681            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  682        );
  683
  684        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  685        editor.end_selection(window, cx);
  686        assert_eq!(
  687            display_ranges(editor, cx),
  688            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  689        );
  690
  691        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  692        editor.end_selection(window, cx);
  693        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  694        assert_eq!(
  695            display_ranges(editor, cx),
  696            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  697        );
  698
  699        editor.update_selection(
  700            DisplayPoint::new(DisplayRow(0), 1),
  701            0,
  702            gpui::Point::<f32>::default(),
  703            window,
  704            cx,
  705        );
  706        editor.end_selection(window, cx);
  707        assert_eq!(
  708            display_ranges(editor, cx),
  709            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  710        );
  711
  712        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  713        editor.end_selection(window, cx);
  714        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  715        editor.end_selection(window, cx);
  716        assert_eq!(
  717            display_ranges(editor, cx),
  718            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  719        );
  720
  721        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  722        assert_eq!(
  723            display_ranges(editor, cx),
  724            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  725        );
  726
  727        editor.update_selection(
  728            DisplayPoint::new(DisplayRow(0), 6),
  729            0,
  730            gpui::Point::<f32>::default(),
  731            window,
  732            cx,
  733        );
  734        assert_eq!(
  735            display_ranges(editor, cx),
  736            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  737        );
  738
  739        editor.update_selection(
  740            DisplayPoint::new(DisplayRow(0), 1),
  741            0,
  742            gpui::Point::<f32>::default(),
  743            window,
  744            cx,
  745        );
  746        editor.end_selection(window, cx);
  747        assert_eq!(
  748            display_ranges(editor, cx),
  749            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  750        );
  751    });
  752}
  753
  754#[gpui::test]
  755fn test_clone(cx: &mut TestAppContext) {
  756    init_test(cx, |_| {});
  757
  758    let (text, selection_ranges) = marked_text_ranges(
  759        indoc! {"
  760            one
  761            two
  762            threeˇ
  763            four
  764            fiveˇ
  765        "},
  766        true,
  767    );
  768
  769    let editor = cx.add_window(|window, cx| {
  770        let buffer = MultiBuffer::build_simple(&text, cx);
  771        build_editor(buffer, window, cx)
  772    });
  773
  774    _ = editor.update(cx, |editor, window, cx| {
  775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  776            s.select_ranges(
  777                selection_ranges
  778                    .iter()
  779                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  780            )
  781        });
  782        editor.fold_creases(
  783            vec![
  784                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  785                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  786            ],
  787            true,
  788            window,
  789            cx,
  790        );
  791    });
  792
  793    let cloned_editor = editor
  794        .update(cx, |editor, _, cx| {
  795            cx.open_window(Default::default(), |window, cx| {
  796                cx.new(|cx| editor.clone(window, cx))
  797            })
  798        })
  799        .unwrap()
  800        .unwrap();
  801
  802    let snapshot = editor
  803        .update(cx, |e, window, cx| e.snapshot(window, cx))
  804        .unwrap();
  805    let cloned_snapshot = cloned_editor
  806        .update(cx, |e, window, cx| e.snapshot(window, cx))
  807        .unwrap();
  808
  809    assert_eq!(
  810        cloned_editor
  811            .update(cx, |e, _, cx| e.display_text(cx))
  812            .unwrap(),
  813        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  814    );
  815    assert_eq!(
  816        cloned_snapshot
  817            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  818            .collect::<Vec<_>>(),
  819        snapshot
  820            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  821            .collect::<Vec<_>>(),
  822    );
  823    assert_set_eq!(
  824        cloned_editor
  825            .update(cx, |editor, _, cx| editor
  826                .selections
  827                .ranges::<Point>(&editor.display_snapshot(cx)))
  828            .unwrap(),
  829        editor
  830            .update(cx, |editor, _, cx| editor
  831                .selections
  832                .ranges(&editor.display_snapshot(cx)))
  833            .unwrap()
  834    );
  835    assert_set_eq!(
  836        cloned_editor
  837            .update(cx, |e, _window, cx| e
  838                .selections
  839                .display_ranges(&e.display_snapshot(cx)))
  840            .unwrap(),
  841        editor
  842            .update(cx, |e, _, cx| e
  843                .selections
  844                .display_ranges(&e.display_snapshot(cx)))
  845            .unwrap()
  846    );
  847}
  848
  849#[gpui::test]
  850async fn test_navigation_history(cx: &mut TestAppContext) {
  851    init_test(cx, |_| {});
  852
  853    use workspace::item::Item;
  854
  855    let fs = FakeFs::new(cx.executor());
  856    let project = Project::test(fs, [], cx).await;
  857    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  858    let pane = workspace
  859        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  860        .unwrap();
  861
  862    _ = workspace.update(cx, |_v, window, cx| {
  863        cx.new(|cx| {
  864            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  865            let mut editor = build_editor(buffer, window, cx);
  866            let handle = cx.entity();
  867            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  868
  869            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  870                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  871            }
  872
  873            // Move the cursor a small distance.
  874            // Nothing is added to the navigation history.
  875            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  876                s.select_display_ranges([
  877                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  878                ])
  879            });
  880            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  881                s.select_display_ranges([
  882                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  883                ])
  884            });
  885            assert!(pop_history(&mut editor, cx).is_none());
  886
  887            // Move the cursor a large distance.
  888            // The history can jump back to the previous position.
  889            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  890                s.select_display_ranges([
  891                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  892                ])
  893            });
  894            let nav_entry = pop_history(&mut editor, cx).unwrap();
  895            editor.navigate(nav_entry.data.unwrap(), window, cx);
  896            assert_eq!(nav_entry.item.id(), cx.entity_id());
  897            assert_eq!(
  898                editor
  899                    .selections
  900                    .display_ranges(&editor.display_snapshot(cx)),
  901                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  902            );
  903            assert!(pop_history(&mut editor, cx).is_none());
  904
  905            // Move the cursor a small distance via the mouse.
  906            // Nothing is added to the navigation history.
  907            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  908            editor.end_selection(window, cx);
  909            assert_eq!(
  910                editor
  911                    .selections
  912                    .display_ranges(&editor.display_snapshot(cx)),
  913                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  914            );
  915            assert!(pop_history(&mut editor, cx).is_none());
  916
  917            // Move the cursor a large distance via the mouse.
  918            // The history can jump back to the previous position.
  919            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  920            editor.end_selection(window, cx);
  921            assert_eq!(
  922                editor
  923                    .selections
  924                    .display_ranges(&editor.display_snapshot(cx)),
  925                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  926            );
  927            let nav_entry = pop_history(&mut editor, cx).unwrap();
  928            editor.navigate(nav_entry.data.unwrap(), window, cx);
  929            assert_eq!(nav_entry.item.id(), cx.entity_id());
  930            assert_eq!(
  931                editor
  932                    .selections
  933                    .display_ranges(&editor.display_snapshot(cx)),
  934                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  935            );
  936            assert!(pop_history(&mut editor, cx).is_none());
  937
  938            // Set scroll position to check later
  939            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  940            let original_scroll_position = editor
  941                .scroll_manager
  942                .native_anchor(&editor.display_snapshot(cx), cx);
  943
  944            // Jump to the end of the document and adjust scroll
  945            editor.move_to_end(&MoveToEnd, window, cx);
  946            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  947            assert_ne!(
  948                editor
  949                    .scroll_manager
  950                    .native_anchor(&editor.display_snapshot(cx), cx),
  951                original_scroll_position
  952            );
  953
  954            let nav_entry = pop_history(&mut editor, cx).unwrap();
  955            editor.navigate(nav_entry.data.unwrap(), window, cx);
  956            assert_eq!(
  957                editor
  958                    .scroll_manager
  959                    .native_anchor(&editor.display_snapshot(cx), cx),
  960                original_scroll_position
  961            );
  962
  963            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  964            let mut invalid_anchor = editor
  965                .scroll_manager
  966                .native_anchor(&editor.display_snapshot(cx), cx)
  967                .anchor;
  968            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  969            let invalid_point = Point::new(9999, 0);
  970            editor.navigate(
  971                Arc::new(NavigationData {
  972                    cursor_anchor: invalid_anchor,
  973                    cursor_position: invalid_point,
  974                    scroll_anchor: ScrollAnchor {
  975                        anchor: invalid_anchor,
  976                        offset: Default::default(),
  977                    },
  978                    scroll_top_row: invalid_point.row,
  979                }),
  980                window,
  981                cx,
  982            );
  983            assert_eq!(
  984                editor
  985                    .selections
  986                    .display_ranges(&editor.display_snapshot(cx)),
  987                &[editor.max_point(cx)..editor.max_point(cx)]
  988            );
  989            assert_eq!(
  990                editor.scroll_position(cx),
  991                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  992            );
  993
  994            editor
  995        })
  996    });
  997}
  998
  999#[gpui::test]
 1000fn test_cancel(cx: &mut TestAppContext) {
 1001    init_test(cx, |_| {});
 1002
 1003    let editor = cx.add_window(|window, cx| {
 1004        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
 1005        build_editor(buffer, window, cx)
 1006    });
 1007
 1008    _ = editor.update(cx, |editor, window, cx| {
 1009        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
 1010        editor.update_selection(
 1011            DisplayPoint::new(DisplayRow(1), 1),
 1012            0,
 1013            gpui::Point::<f32>::default(),
 1014            window,
 1015            cx,
 1016        );
 1017        editor.end_selection(window, cx);
 1018
 1019        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1020        editor.update_selection(
 1021            DisplayPoint::new(DisplayRow(0), 3),
 1022            0,
 1023            gpui::Point::<f32>::default(),
 1024            window,
 1025            cx,
 1026        );
 1027        editor.end_selection(window, cx);
 1028        assert_eq!(
 1029            display_ranges(editor, cx),
 1030            [
 1031                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1032                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1033            ]
 1034        );
 1035    });
 1036
 1037    _ = editor.update(cx, |editor, window, cx| {
 1038        editor.cancel(&Cancel, window, cx);
 1039        assert_eq!(
 1040            display_ranges(editor, cx),
 1041            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1042        );
 1043    });
 1044
 1045    _ = editor.update(cx, |editor, window, cx| {
 1046        editor.cancel(&Cancel, window, cx);
 1047        assert_eq!(
 1048            display_ranges(editor, cx),
 1049            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1050        );
 1051    });
 1052}
 1053
 1054#[gpui::test]
 1055fn test_fold_action(cx: &mut TestAppContext) {
 1056    init_test(cx, |_| {});
 1057
 1058    let editor = cx.add_window(|window, cx| {
 1059        let buffer = MultiBuffer::build_simple(
 1060            &"
 1061                impl Foo {
 1062                    // Hello!
 1063
 1064                    fn a() {
 1065                        1
 1066                    }
 1067
 1068                    fn b() {
 1069                        2
 1070                    }
 1071
 1072                    fn c() {
 1073                        3
 1074                    }
 1075                }
 1076            "
 1077            .unindent(),
 1078            cx,
 1079        );
 1080        build_editor(buffer, window, cx)
 1081    });
 1082
 1083    _ = editor.update(cx, |editor, window, cx| {
 1084        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1085            s.select_display_ranges([
 1086                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1087            ]);
 1088        });
 1089        editor.fold(&Fold, window, cx);
 1090        assert_eq!(
 1091            editor.display_text(cx),
 1092            "
 1093                impl Foo {
 1094                    // Hello!
 1095
 1096                    fn a() {
 1097                        1
 1098                    }
 1099
 1100                    fn b() {⋯
 1101                    }
 1102
 1103                    fn c() {⋯
 1104                    }
 1105                }
 1106            "
 1107            .unindent(),
 1108        );
 1109
 1110        editor.fold(&Fold, window, cx);
 1111        assert_eq!(
 1112            editor.display_text(cx),
 1113            "
 1114                impl Foo {⋯
 1115                }
 1116            "
 1117            .unindent(),
 1118        );
 1119
 1120        editor.unfold_lines(&UnfoldLines, window, cx);
 1121        assert_eq!(
 1122            editor.display_text(cx),
 1123            "
 1124                impl Foo {
 1125                    // Hello!
 1126
 1127                    fn a() {
 1128                        1
 1129                    }
 1130
 1131                    fn b() {⋯
 1132                    }
 1133
 1134                    fn c() {⋯
 1135                    }
 1136                }
 1137            "
 1138            .unindent(),
 1139        );
 1140
 1141        editor.unfold_lines(&UnfoldLines, window, cx);
 1142        assert_eq!(
 1143            editor.display_text(cx),
 1144            editor.buffer.read(cx).read(cx).text()
 1145        );
 1146    });
 1147}
 1148
 1149#[gpui::test]
 1150fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1151    init_test(cx, |_| {});
 1152
 1153    let editor = cx.add_window(|window, cx| {
 1154        let buffer = MultiBuffer::build_simple(
 1155            &"
 1156                class Foo:
 1157                    # Hello!
 1158
 1159                    def a():
 1160                        print(1)
 1161
 1162                    def b():
 1163                        print(2)
 1164
 1165                    def c():
 1166                        print(3)
 1167            "
 1168            .unindent(),
 1169            cx,
 1170        );
 1171        build_editor(buffer, window, cx)
 1172    });
 1173
 1174    _ = editor.update(cx, |editor, window, cx| {
 1175        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1176            s.select_display_ranges([
 1177                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1178            ]);
 1179        });
 1180        editor.fold(&Fold, window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():
 1188                        print(1)
 1189
 1190                    def b():⋯
 1191
 1192                    def c():⋯
 1193            "
 1194            .unindent(),
 1195        );
 1196
 1197        editor.fold(&Fold, window, cx);
 1198        assert_eq!(
 1199            editor.display_text(cx),
 1200            "
 1201                class Foo:⋯
 1202            "
 1203            .unindent(),
 1204        );
 1205
 1206        editor.unfold_lines(&UnfoldLines, window, cx);
 1207        assert_eq!(
 1208            editor.display_text(cx),
 1209            "
 1210                class Foo:
 1211                    # Hello!
 1212
 1213                    def a():
 1214                        print(1)
 1215
 1216                    def b():⋯
 1217
 1218                    def c():⋯
 1219            "
 1220            .unindent(),
 1221        );
 1222
 1223        editor.unfold_lines(&UnfoldLines, window, cx);
 1224        assert_eq!(
 1225            editor.display_text(cx),
 1226            editor.buffer.read(cx).read(cx).text()
 1227        );
 1228    });
 1229}
 1230
 1231#[gpui::test]
 1232fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1233    init_test(cx, |_| {});
 1234
 1235    let editor = cx.add_window(|window, cx| {
 1236        let buffer = MultiBuffer::build_simple(
 1237            &"
 1238                class Foo:
 1239                    # Hello!
 1240
 1241                    def a():
 1242                        print(1)
 1243
 1244                    def b():
 1245                        print(2)
 1246
 1247
 1248                    def c():
 1249                        print(3)
 1250
 1251
 1252            "
 1253            .unindent(),
 1254            cx,
 1255        );
 1256        build_editor(buffer, window, cx)
 1257    });
 1258
 1259    _ = editor.update(cx, |editor, window, cx| {
 1260        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1261            s.select_display_ranges([
 1262                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1263            ]);
 1264        });
 1265        editor.fold(&Fold, window, cx);
 1266        assert_eq!(
 1267            editor.display_text(cx),
 1268            "
 1269                class Foo:
 1270                    # Hello!
 1271
 1272                    def a():
 1273                        print(1)
 1274
 1275                    def b():⋯
 1276
 1277
 1278                    def c():⋯
 1279
 1280
 1281            "
 1282            .unindent(),
 1283        );
 1284
 1285        editor.fold(&Fold, window, cx);
 1286        assert_eq!(
 1287            editor.display_text(cx),
 1288            "
 1289                class Foo:⋯
 1290
 1291
 1292            "
 1293            .unindent(),
 1294        );
 1295
 1296        editor.unfold_lines(&UnfoldLines, window, cx);
 1297        assert_eq!(
 1298            editor.display_text(cx),
 1299            "
 1300                class Foo:
 1301                    # Hello!
 1302
 1303                    def a():
 1304                        print(1)
 1305
 1306                    def b():⋯
 1307
 1308
 1309                    def c():⋯
 1310
 1311
 1312            "
 1313            .unindent(),
 1314        );
 1315
 1316        editor.unfold_lines(&UnfoldLines, window, cx);
 1317        assert_eq!(
 1318            editor.display_text(cx),
 1319            editor.buffer.read(cx).read(cx).text()
 1320        );
 1321    });
 1322}
 1323
 1324#[gpui::test]
 1325fn test_fold_at_level(cx: &mut TestAppContext) {
 1326    init_test(cx, |_| {});
 1327
 1328    let editor = cx.add_window(|window, cx| {
 1329        let buffer = MultiBuffer::build_simple(
 1330            &"
 1331                class Foo:
 1332                    # Hello!
 1333
 1334                    def a():
 1335                        print(1)
 1336
 1337                    def b():
 1338                        print(2)
 1339
 1340
 1341                class Bar:
 1342                    # World!
 1343
 1344                    def a():
 1345                        print(1)
 1346
 1347                    def b():
 1348                        print(2)
 1349
 1350
 1351            "
 1352            .unindent(),
 1353            cx,
 1354        );
 1355        build_editor(buffer, window, cx)
 1356    });
 1357
 1358    _ = editor.update(cx, |editor, window, cx| {
 1359        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1360        assert_eq!(
 1361            editor.display_text(cx),
 1362            "
 1363                class Foo:
 1364                    # Hello!
 1365
 1366                    def a():⋯
 1367
 1368                    def b():⋯
 1369
 1370
 1371                class Bar:
 1372                    # World!
 1373
 1374                    def a():⋯
 1375
 1376                    def b():⋯
 1377
 1378
 1379            "
 1380            .unindent(),
 1381        );
 1382
 1383        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1384        assert_eq!(
 1385            editor.display_text(cx),
 1386            "
 1387                class Foo:⋯
 1388
 1389
 1390                class Bar:⋯
 1391
 1392
 1393            "
 1394            .unindent(),
 1395        );
 1396
 1397        editor.unfold_all(&UnfoldAll, window, cx);
 1398        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1399        assert_eq!(
 1400            editor.display_text(cx),
 1401            "
 1402                class Foo:
 1403                    # Hello!
 1404
 1405                    def a():
 1406                        print(1)
 1407
 1408                    def b():
 1409                        print(2)
 1410
 1411
 1412                class Bar:
 1413                    # World!
 1414
 1415                    def a():
 1416                        print(1)
 1417
 1418                    def b():
 1419                        print(2)
 1420
 1421
 1422            "
 1423            .unindent(),
 1424        );
 1425
 1426        assert_eq!(
 1427            editor.display_text(cx),
 1428            editor.buffer.read(cx).read(cx).text()
 1429        );
 1430        let (_, positions) = marked_text_ranges(
 1431            &"
 1432                       class Foo:
 1433                           # Hello!
 1434
 1435                           def a():
 1436                              print(1)
 1437
 1438                           def b():
 1439                               p«riˇ»nt(2)
 1440
 1441
 1442                       class Bar:
 1443                           # World!
 1444
 1445                           def a():
 1446                               «ˇprint(1)
 1447
 1448                           def b():
 1449                               print(2)»
 1450
 1451
 1452                   "
 1453            .unindent(),
 1454            true,
 1455        );
 1456
 1457        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1458            s.select_ranges(
 1459                positions
 1460                    .iter()
 1461                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1462            )
 1463        });
 1464
 1465        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1466        assert_eq!(
 1467            editor.display_text(cx),
 1468            "
 1469                class Foo:
 1470                    # Hello!
 1471
 1472                    def a():⋯
 1473
 1474                    def b():
 1475                        print(2)
 1476
 1477
 1478                class Bar:
 1479                    # World!
 1480
 1481                    def a():
 1482                        print(1)
 1483
 1484                    def b():
 1485                        print(2)
 1486
 1487
 1488            "
 1489            .unindent(),
 1490        );
 1491    });
 1492}
 1493
 1494#[gpui::test]
 1495fn test_move_cursor(cx: &mut TestAppContext) {
 1496    init_test(cx, |_| {});
 1497
 1498    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1499    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1500
 1501    buffer.update(cx, |buffer, cx| {
 1502        buffer.edit(
 1503            vec![
 1504                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1505                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1506            ],
 1507            None,
 1508            cx,
 1509        );
 1510    });
 1511    _ = editor.update(cx, |editor, window, cx| {
 1512        assert_eq!(
 1513            display_ranges(editor, cx),
 1514            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1515        );
 1516
 1517        editor.move_down(&MoveDown, window, cx);
 1518        assert_eq!(
 1519            display_ranges(editor, cx),
 1520            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1521        );
 1522
 1523        editor.move_right(&MoveRight, window, cx);
 1524        assert_eq!(
 1525            display_ranges(editor, cx),
 1526            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1527        );
 1528
 1529        editor.move_left(&MoveLeft, window, cx);
 1530        assert_eq!(
 1531            display_ranges(editor, cx),
 1532            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1533        );
 1534
 1535        editor.move_up(&MoveUp, window, cx);
 1536        assert_eq!(
 1537            display_ranges(editor, cx),
 1538            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1539        );
 1540
 1541        editor.move_to_end(&MoveToEnd, window, cx);
 1542        assert_eq!(
 1543            display_ranges(editor, cx),
 1544            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1545        );
 1546
 1547        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1548        assert_eq!(
 1549            display_ranges(editor, cx),
 1550            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1551        );
 1552
 1553        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1554            s.select_display_ranges([
 1555                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1556            ]);
 1557        });
 1558        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1559        assert_eq!(
 1560            display_ranges(editor, cx),
 1561            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1562        );
 1563
 1564        editor.select_to_end(&SelectToEnd, window, cx);
 1565        assert_eq!(
 1566            display_ranges(editor, cx),
 1567            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1568        );
 1569    });
 1570}
 1571
 1572#[gpui::test]
 1573fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1574    init_test(cx, |_| {});
 1575
 1576    let editor = cx.add_window(|window, cx| {
 1577        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1578        build_editor(buffer, window, cx)
 1579    });
 1580
 1581    assert_eq!('🟥'.len_utf8(), 4);
 1582    assert_eq!('α'.len_utf8(), 2);
 1583
 1584    _ = editor.update(cx, |editor, window, cx| {
 1585        editor.fold_creases(
 1586            vec![
 1587                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1588                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1589                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1590            ],
 1591            true,
 1592            window,
 1593            cx,
 1594        );
 1595        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1596
 1597        editor.move_right(&MoveRight, window, cx);
 1598        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1599        editor.move_right(&MoveRight, window, cx);
 1600        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1601        editor.move_right(&MoveRight, window, cx);
 1602        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1603
 1604        editor.move_down(&MoveDown, window, cx);
 1605        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1606        editor.move_left(&MoveLeft, window, cx);
 1607        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1608        editor.move_left(&MoveLeft, window, cx);
 1609        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1610        editor.move_left(&MoveLeft, window, cx);
 1611        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1612
 1613        editor.move_down(&MoveDown, window, cx);
 1614        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1615        editor.move_right(&MoveRight, window, cx);
 1616        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1617        editor.move_right(&MoveRight, window, cx);
 1618        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1619        editor.move_right(&MoveRight, window, cx);
 1620        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1621
 1622        editor.move_up(&MoveUp, window, cx);
 1623        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1624        editor.move_down(&MoveDown, window, cx);
 1625        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1626        editor.move_up(&MoveUp, window, cx);
 1627        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1628
 1629        editor.move_up(&MoveUp, window, cx);
 1630        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1631        editor.move_left(&MoveLeft, window, cx);
 1632        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1633        editor.move_left(&MoveLeft, window, cx);
 1634        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1635    });
 1636}
 1637
 1638#[gpui::test]
 1639fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1640    init_test(cx, |_| {});
 1641
 1642    let editor = cx.add_window(|window, cx| {
 1643        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1644        build_editor(buffer, window, cx)
 1645    });
 1646    _ = editor.update(cx, |editor, window, cx| {
 1647        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1648            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1649        });
 1650
 1651        // moving above start of document should move selection to start of document,
 1652        // but the next move down should still be at the original goal_x
 1653        editor.move_up(&MoveUp, window, cx);
 1654        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1655
 1656        editor.move_down(&MoveDown, window, cx);
 1657        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1658
 1659        editor.move_down(&MoveDown, window, cx);
 1660        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1661
 1662        editor.move_down(&MoveDown, window, cx);
 1663        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1664
 1665        editor.move_down(&MoveDown, window, cx);
 1666        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1667
 1668        // moving past end of document should not change goal_x
 1669        editor.move_down(&MoveDown, window, cx);
 1670        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1671
 1672        editor.move_down(&MoveDown, window, cx);
 1673        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1674
 1675        editor.move_up(&MoveUp, window, cx);
 1676        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1677
 1678        editor.move_up(&MoveUp, window, cx);
 1679        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1680
 1681        editor.move_up(&MoveUp, window, cx);
 1682        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1683    });
 1684}
 1685
 1686#[gpui::test]
 1687fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1688    init_test(cx, |_| {});
 1689    let move_to_beg = MoveToBeginningOfLine {
 1690        stop_at_soft_wraps: true,
 1691        stop_at_indent: true,
 1692    };
 1693
 1694    let delete_to_beg = DeleteToBeginningOfLine {
 1695        stop_at_indent: false,
 1696    };
 1697
 1698    let move_to_end = MoveToEndOfLine {
 1699        stop_at_soft_wraps: true,
 1700    };
 1701
 1702    let editor = cx.add_window(|window, cx| {
 1703        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1704        build_editor(buffer, window, cx)
 1705    });
 1706    _ = editor.update(cx, |editor, window, cx| {
 1707        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1708            s.select_display_ranges([
 1709                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1710                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1711            ]);
 1712        });
 1713    });
 1714
 1715    _ = editor.update(cx, |editor, window, cx| {
 1716        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1717        assert_eq!(
 1718            display_ranges(editor, cx),
 1719            &[
 1720                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1721                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1722            ]
 1723        );
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1728        assert_eq!(
 1729            display_ranges(editor, cx),
 1730            &[
 1731                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1732                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1733            ]
 1734        );
 1735    });
 1736
 1737    _ = editor.update(cx, |editor, window, cx| {
 1738        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1739        assert_eq!(
 1740            display_ranges(editor, cx),
 1741            &[
 1742                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1743                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1744            ]
 1745        );
 1746    });
 1747
 1748    _ = editor.update(cx, |editor, window, cx| {
 1749        editor.move_to_end_of_line(&move_to_end, window, cx);
 1750        assert_eq!(
 1751            display_ranges(editor, cx),
 1752            &[
 1753                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1754                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1755            ]
 1756        );
 1757    });
 1758
 1759    // Moving to the end of line again is a no-op.
 1760    _ = editor.update(cx, |editor, window, cx| {
 1761        editor.move_to_end_of_line(&move_to_end, window, cx);
 1762        assert_eq!(
 1763            display_ranges(editor, cx),
 1764            &[
 1765                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1766                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1767            ]
 1768        );
 1769    });
 1770
 1771    _ = editor.update(cx, |editor, window, cx| {
 1772        editor.move_left(&MoveLeft, window, cx);
 1773        editor.select_to_beginning_of_line(
 1774            &SelectToBeginningOfLine {
 1775                stop_at_soft_wraps: true,
 1776                stop_at_indent: true,
 1777            },
 1778            window,
 1779            cx,
 1780        );
 1781        assert_eq!(
 1782            display_ranges(editor, cx),
 1783            &[
 1784                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1785                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1786            ]
 1787        );
 1788    });
 1789
 1790    _ = editor.update(cx, |editor, window, cx| {
 1791        editor.select_to_beginning_of_line(
 1792            &SelectToBeginningOfLine {
 1793                stop_at_soft_wraps: true,
 1794                stop_at_indent: true,
 1795            },
 1796            window,
 1797            cx,
 1798        );
 1799        assert_eq!(
 1800            display_ranges(editor, cx),
 1801            &[
 1802                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1803                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1804            ]
 1805        );
 1806    });
 1807
 1808    _ = editor.update(cx, |editor, window, cx| {
 1809        editor.select_to_beginning_of_line(
 1810            &SelectToBeginningOfLine {
 1811                stop_at_soft_wraps: true,
 1812                stop_at_indent: 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), 0),
 1821                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1822            ]
 1823        );
 1824    });
 1825
 1826    _ = editor.update(cx, |editor, window, cx| {
 1827        editor.select_to_end_of_line(
 1828            &SelectToEndOfLine {
 1829                stop_at_soft_wraps: true,
 1830            },
 1831            window,
 1832            cx,
 1833        );
 1834        assert_eq!(
 1835            display_ranges(editor, cx),
 1836            &[
 1837                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1838                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1839            ]
 1840        );
 1841    });
 1842
 1843    _ = editor.update(cx, |editor, window, cx| {
 1844        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1845        assert_eq!(editor.display_text(cx), "ab\n  de");
 1846        assert_eq!(
 1847            display_ranges(editor, cx),
 1848            &[
 1849                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1850                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1851            ]
 1852        );
 1853    });
 1854
 1855    _ = editor.update(cx, |editor, window, cx| {
 1856        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1857        assert_eq!(editor.display_text(cx), "\n");
 1858        assert_eq!(
 1859            display_ranges(editor, cx),
 1860            &[
 1861                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1862                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1863            ]
 1864        );
 1865    });
 1866}
 1867
 1868#[gpui::test]
 1869fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1870    init_test(cx, |_| {});
 1871    let move_to_beg = MoveToBeginningOfLine {
 1872        stop_at_soft_wraps: false,
 1873        stop_at_indent: false,
 1874    };
 1875
 1876    let move_to_end = MoveToEndOfLine {
 1877        stop_at_soft_wraps: false,
 1878    };
 1879
 1880    let editor = cx.add_window(|window, cx| {
 1881        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1882        build_editor(buffer, window, cx)
 1883    });
 1884
 1885    _ = editor.update(cx, |editor, window, cx| {
 1886        editor.set_wrap_width(Some(140.0.into()), cx);
 1887
 1888        // We expect the following lines after wrapping
 1889        // ```
 1890        // thequickbrownfox
 1891        // jumpedoverthelazydo
 1892        // gs
 1893        // ```
 1894        // The final `gs` was soft-wrapped onto a new line.
 1895        assert_eq!(
 1896            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1897            editor.display_text(cx),
 1898        );
 1899
 1900        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1901        // Start the cursor at the `k` on the first line
 1902        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1903            s.select_display_ranges([
 1904                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1905            ]);
 1906        });
 1907
 1908        // Moving to the beginning of the line should put us at the beginning of the line.
 1909        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1910        assert_eq!(
 1911            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1912            display_ranges(editor, cx)
 1913        );
 1914
 1915        // Moving to the end of the line should put us at the end of the line.
 1916        editor.move_to_end_of_line(&move_to_end, window, cx);
 1917        assert_eq!(
 1918            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1919            display_ranges(editor, cx)
 1920        );
 1921
 1922        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1923        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1924        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1925            s.select_display_ranges([
 1926                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1927            ]);
 1928        });
 1929
 1930        // Moving to the beginning of the line should put us at the start of the second line of
 1931        // display text, i.e., the `j`.
 1932        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1933        assert_eq!(
 1934            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1935            display_ranges(editor, cx)
 1936        );
 1937
 1938        // Moving to the beginning of the line again should be a no-op.
 1939        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1940        assert_eq!(
 1941            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1942            display_ranges(editor, cx)
 1943        );
 1944
 1945        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1946        // next display line.
 1947        editor.move_to_end_of_line(&move_to_end, window, cx);
 1948        assert_eq!(
 1949            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1950            display_ranges(editor, cx)
 1951        );
 1952
 1953        // Moving to the end of the line again should be a no-op.
 1954        editor.move_to_end_of_line(&move_to_end, window, cx);
 1955        assert_eq!(
 1956            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1957            display_ranges(editor, cx)
 1958        );
 1959    });
 1960}
 1961
 1962#[gpui::test]
 1963fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1964    init_test(cx, |_| {});
 1965
 1966    let move_to_beg = MoveToBeginningOfLine {
 1967        stop_at_soft_wraps: true,
 1968        stop_at_indent: true,
 1969    };
 1970
 1971    let select_to_beg = SelectToBeginningOfLine {
 1972        stop_at_soft_wraps: true,
 1973        stop_at_indent: true,
 1974    };
 1975
 1976    let delete_to_beg = DeleteToBeginningOfLine {
 1977        stop_at_indent: true,
 1978    };
 1979
 1980    let move_to_end = MoveToEndOfLine {
 1981        stop_at_soft_wraps: false,
 1982    };
 1983
 1984    let editor = cx.add_window(|window, cx| {
 1985        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1986        build_editor(buffer, window, cx)
 1987    });
 1988
 1989    _ = editor.update(cx, |editor, window, cx| {
 1990        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1991            s.select_display_ranges([
 1992                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1993                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1994            ]);
 1995        });
 1996
 1997        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1998        // and the second cursor at the first non-whitespace character in the line.
 1999        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2000        assert_eq!(
 2001            display_ranges(editor, cx),
 2002            &[
 2003                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2004                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2005            ]
 2006        );
 2007
 2008        // Moving to the beginning of the line again should be a no-op for the first cursor,
 2009        // and should move the second cursor to the beginning of the line.
 2010        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2011        assert_eq!(
 2012            display_ranges(editor, cx),
 2013            &[
 2014                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2015                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2016            ]
 2017        );
 2018
 2019        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2020        // and should move the second cursor back to the first non-whitespace character in the line.
 2021        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2022        assert_eq!(
 2023            display_ranges(editor, cx),
 2024            &[
 2025                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2026                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2027            ]
 2028        );
 2029
 2030        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2031        // and to the first non-whitespace character in the line for the second cursor.
 2032        editor.move_to_end_of_line(&move_to_end, window, cx);
 2033        editor.move_left(&MoveLeft, window, cx);
 2034        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2035        assert_eq!(
 2036            display_ranges(editor, cx),
 2037            &[
 2038                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2039                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2040            ]
 2041        );
 2042
 2043        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2044        // and should select to the beginning of the line for the second cursor.
 2045        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2046        assert_eq!(
 2047            display_ranges(editor, cx),
 2048            &[
 2049                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2050                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2051            ]
 2052        );
 2053
 2054        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2055        // and should delete to the first non-whitespace character in the line for the second cursor.
 2056        editor.move_to_end_of_line(&move_to_end, window, cx);
 2057        editor.move_left(&MoveLeft, window, cx);
 2058        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2059        assert_eq!(editor.text(cx), "c\n  f");
 2060    });
 2061}
 2062
 2063#[gpui::test]
 2064fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2065    init_test(cx, |_| {});
 2066
 2067    let move_to_beg = MoveToBeginningOfLine {
 2068        stop_at_soft_wraps: true,
 2069        stop_at_indent: true,
 2070    };
 2071
 2072    let editor = cx.add_window(|window, cx| {
 2073        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2074        build_editor(buffer, window, cx)
 2075    });
 2076
 2077    _ = editor.update(cx, |editor, window, cx| {
 2078        // test cursor between line_start and indent_start
 2079        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2080            s.select_display_ranges([
 2081                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2082            ]);
 2083        });
 2084
 2085        // cursor should move to line_start
 2086        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2087        assert_eq!(
 2088            display_ranges(editor, cx),
 2089            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2090        );
 2091
 2092        // cursor should move to indent_start
 2093        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2094        assert_eq!(
 2095            display_ranges(editor, cx),
 2096            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2097        );
 2098
 2099        // cursor should move to back to line_start
 2100        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2101        assert_eq!(
 2102            display_ranges(editor, cx),
 2103            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2104        );
 2105    });
 2106}
 2107
 2108#[gpui::test]
 2109fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2110    init_test(cx, |_| {});
 2111
 2112    let editor = cx.add_window(|window, cx| {
 2113        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2114        build_editor(buffer, window, cx)
 2115    });
 2116    _ = editor.update(cx, |editor, window, cx| {
 2117        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2118            s.select_display_ranges([
 2119                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2120                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2121            ])
 2122        });
 2123        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2124        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2125
 2126        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2127        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2128
 2129        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2130        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2131
 2132        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2133        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2134
 2135        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2136        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2137
 2138        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2139        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2140
 2141        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2142        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2143
 2144        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2145        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2146
 2147        editor.move_right(&MoveRight, window, cx);
 2148        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2149        assert_selection_ranges(
 2150            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2151            editor,
 2152            cx,
 2153        );
 2154
 2155        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2156        assert_selection_ranges(
 2157            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2158            editor,
 2159            cx,
 2160        );
 2161
 2162        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2163        assert_selection_ranges(
 2164            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2165            editor,
 2166            cx,
 2167        );
 2168    });
 2169}
 2170
 2171#[gpui::test]
 2172fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2173    init_test(cx, |_| {});
 2174
 2175    let editor = cx.add_window(|window, cx| {
 2176        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2177        build_editor(buffer, window, cx)
 2178    });
 2179
 2180    _ = editor.update(cx, |editor, window, cx| {
 2181        editor.set_wrap_width(Some(140.0.into()), cx);
 2182        assert_eq!(
 2183            editor.display_text(cx),
 2184            "use one::{\n    two::three::\n    four::five\n};"
 2185        );
 2186
 2187        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2188            s.select_display_ranges([
 2189                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2190            ]);
 2191        });
 2192
 2193        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2194        assert_eq!(
 2195            display_ranges(editor, cx),
 2196            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2197        );
 2198
 2199        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2200        assert_eq!(
 2201            display_ranges(editor, cx),
 2202            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2203        );
 2204
 2205        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2206        assert_eq!(
 2207            display_ranges(editor, cx),
 2208            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2209        );
 2210
 2211        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2212        assert_eq!(
 2213            display_ranges(editor, cx),
 2214            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2215        );
 2216
 2217        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2218        assert_eq!(
 2219            display_ranges(editor, cx),
 2220            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2221        );
 2222
 2223        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2224        assert_eq!(
 2225            display_ranges(editor, cx),
 2226            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2227        );
 2228    });
 2229}
 2230
 2231#[gpui::test]
 2232async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2233    init_test(cx, |_| {});
 2234    let mut cx = EditorTestContext::new(cx).await;
 2235
 2236    let line_height = cx.update_editor(|editor, window, cx| {
 2237        editor
 2238            .style(cx)
 2239            .text
 2240            .line_height_in_pixels(window.rem_size())
 2241    });
 2242    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2243
 2244    cx.set_state(
 2245        &r#"ˇone
 2246        two
 2247
 2248        three
 2249        fourˇ
 2250        five
 2251
 2252        six"#
 2253            .unindent(),
 2254    );
 2255
 2256    cx.update_editor(|editor, window, cx| {
 2257        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2258    });
 2259    cx.assert_editor_state(
 2260        &r#"one
 2261        two
 2262        ˇ
 2263        three
 2264        four
 2265        five
 2266        ˇ
 2267        six"#
 2268            .unindent(),
 2269    );
 2270
 2271    cx.update_editor(|editor, window, cx| {
 2272        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2273    });
 2274    cx.assert_editor_state(
 2275        &r#"one
 2276        two
 2277
 2278        three
 2279        four
 2280        five
 2281        ˇ
 2282        sixˇ"#
 2283            .unindent(),
 2284    );
 2285
 2286    cx.update_editor(|editor, window, cx| {
 2287        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2288    });
 2289    cx.assert_editor_state(
 2290        &r#"one
 2291        two
 2292
 2293        three
 2294        four
 2295        five
 2296
 2297        sixˇ"#
 2298            .unindent(),
 2299    );
 2300
 2301    cx.update_editor(|editor, window, cx| {
 2302        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2303    });
 2304    cx.assert_editor_state(
 2305        &r#"one
 2306        two
 2307
 2308        three
 2309        four
 2310        five
 2311        ˇ
 2312        six"#
 2313            .unindent(),
 2314    );
 2315
 2316    cx.update_editor(|editor, window, cx| {
 2317        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2318    });
 2319    cx.assert_editor_state(
 2320        &r#"one
 2321        two
 2322        ˇ
 2323        three
 2324        four
 2325        five
 2326
 2327        six"#
 2328            .unindent(),
 2329    );
 2330
 2331    cx.update_editor(|editor, window, cx| {
 2332        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2333    });
 2334    cx.assert_editor_state(
 2335        &r#"ˇone
 2336        two
 2337
 2338        three
 2339        four
 2340        five
 2341
 2342        six"#
 2343            .unindent(),
 2344    );
 2345}
 2346
 2347#[gpui::test]
 2348async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2349    init_test(cx, |_| {});
 2350    let mut cx = EditorTestContext::new(cx).await;
 2351    let line_height = cx.update_editor(|editor, window, cx| {
 2352        editor
 2353            .style(cx)
 2354            .text
 2355            .line_height_in_pixels(window.rem_size())
 2356    });
 2357    let window = cx.window;
 2358    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2359
 2360    cx.set_state(
 2361        r#"ˇone
 2362        two
 2363        three
 2364        four
 2365        five
 2366        six
 2367        seven
 2368        eight
 2369        nine
 2370        ten
 2371        "#,
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| {
 2375        assert_eq!(
 2376            editor.snapshot(window, cx).scroll_position(),
 2377            gpui::Point::new(0., 0.)
 2378        );
 2379        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2380        assert_eq!(
 2381            editor.snapshot(window, cx).scroll_position(),
 2382            gpui::Point::new(0., 3.)
 2383        );
 2384        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2385        assert_eq!(
 2386            editor.snapshot(window, cx).scroll_position(),
 2387            gpui::Point::new(0., 6.)
 2388        );
 2389        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2390        assert_eq!(
 2391            editor.snapshot(window, cx).scroll_position(),
 2392            gpui::Point::new(0., 3.)
 2393        );
 2394
 2395        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2396        assert_eq!(
 2397            editor.snapshot(window, cx).scroll_position(),
 2398            gpui::Point::new(0., 1.)
 2399        );
 2400        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2401        assert_eq!(
 2402            editor.snapshot(window, cx).scroll_position(),
 2403            gpui::Point::new(0., 3.)
 2404        );
 2405    });
 2406}
 2407
 2408#[gpui::test]
 2409async fn test_autoscroll(cx: &mut TestAppContext) {
 2410    init_test(cx, |_| {});
 2411    let mut cx = EditorTestContext::new(cx).await;
 2412
 2413    let line_height = cx.update_editor(|editor, window, cx| {
 2414        editor.set_vertical_scroll_margin(2, cx);
 2415        editor
 2416            .style(cx)
 2417            .text
 2418            .line_height_in_pixels(window.rem_size())
 2419    });
 2420    let window = cx.window;
 2421    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2422
 2423    cx.set_state(
 2424        r#"ˇone
 2425            two
 2426            three
 2427            four
 2428            five
 2429            six
 2430            seven
 2431            eight
 2432            nine
 2433            ten
 2434        "#,
 2435    );
 2436    cx.update_editor(|editor, window, cx| {
 2437        assert_eq!(
 2438            editor.snapshot(window, cx).scroll_position(),
 2439            gpui::Point::new(0., 0.0)
 2440        );
 2441    });
 2442
 2443    // Add a cursor below the visible area. Since both cursors cannot fit
 2444    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2445    // allows the vertical scroll margin below that cursor.
 2446    cx.update_editor(|editor, window, cx| {
 2447        editor.change_selections(Default::default(), window, cx, |selections| {
 2448            selections.select_ranges([
 2449                Point::new(0, 0)..Point::new(0, 0),
 2450                Point::new(6, 0)..Point::new(6, 0),
 2451            ]);
 2452        })
 2453    });
 2454    cx.update_editor(|editor, window, cx| {
 2455        assert_eq!(
 2456            editor.snapshot(window, cx).scroll_position(),
 2457            gpui::Point::new(0., 3.0)
 2458        );
 2459    });
 2460
 2461    // Move down. The editor cursor scrolls down to track the newest cursor.
 2462    cx.update_editor(|editor, window, cx| {
 2463        editor.move_down(&Default::default(), window, cx);
 2464    });
 2465    cx.update_editor(|editor, window, cx| {
 2466        assert_eq!(
 2467            editor.snapshot(window, cx).scroll_position(),
 2468            gpui::Point::new(0., 4.0)
 2469        );
 2470    });
 2471
 2472    // Add a cursor above the visible area. Since both cursors fit on screen,
 2473    // the editor scrolls to show both.
 2474    cx.update_editor(|editor, window, cx| {
 2475        editor.change_selections(Default::default(), window, cx, |selections| {
 2476            selections.select_ranges([
 2477                Point::new(1, 0)..Point::new(1, 0),
 2478                Point::new(6, 0)..Point::new(6, 0),
 2479            ]);
 2480        })
 2481    });
 2482    cx.update_editor(|editor, window, cx| {
 2483        assert_eq!(
 2484            editor.snapshot(window, cx).scroll_position(),
 2485            gpui::Point::new(0., 1.0)
 2486        );
 2487    });
 2488}
 2489
 2490#[gpui::test]
 2491async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2492    init_test(cx, |_| {});
 2493    let mut cx = EditorTestContext::new(cx).await;
 2494
 2495    let line_height = cx.update_editor(|editor, window, cx| {
 2496        editor
 2497            .style(cx)
 2498            .text
 2499            .line_height_in_pixels(window.rem_size())
 2500    });
 2501    let window = cx.window;
 2502    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2503    cx.set_state(
 2504        &r#"
 2505        ˇone
 2506        two
 2507        threeˇ
 2508        four
 2509        five
 2510        six
 2511        seven
 2512        eight
 2513        nine
 2514        ten
 2515        "#
 2516        .unindent(),
 2517    );
 2518
 2519    cx.update_editor(|editor, window, cx| {
 2520        editor.move_page_down(&MovePageDown::default(), window, cx)
 2521    });
 2522    cx.assert_editor_state(
 2523        &r#"
 2524        one
 2525        two
 2526        three
 2527        ˇfour
 2528        five
 2529        sixˇ
 2530        seven
 2531        eight
 2532        nine
 2533        ten
 2534        "#
 2535        .unindent(),
 2536    );
 2537
 2538    cx.update_editor(|editor, window, cx| {
 2539        editor.move_page_down(&MovePageDown::default(), window, cx)
 2540    });
 2541    cx.assert_editor_state(
 2542        &r#"
 2543        one
 2544        two
 2545        three
 2546        four
 2547        five
 2548        six
 2549        ˇseven
 2550        eight
 2551        nineˇ
 2552        ten
 2553        "#
 2554        .unindent(),
 2555    );
 2556
 2557    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2558    cx.assert_editor_state(
 2559        &r#"
 2560        one
 2561        two
 2562        three
 2563        ˇfour
 2564        five
 2565        sixˇ
 2566        seven
 2567        eight
 2568        nine
 2569        ten
 2570        "#
 2571        .unindent(),
 2572    );
 2573
 2574    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2575    cx.assert_editor_state(
 2576        &r#"
 2577        ˇone
 2578        two
 2579        threeˇ
 2580        four
 2581        five
 2582        six
 2583        seven
 2584        eight
 2585        nine
 2586        ten
 2587        "#
 2588        .unindent(),
 2589    );
 2590
 2591    // Test select collapsing
 2592    cx.update_editor(|editor, window, cx| {
 2593        editor.move_page_down(&MovePageDown::default(), window, cx);
 2594        editor.move_page_down(&MovePageDown::default(), window, cx);
 2595        editor.move_page_down(&MovePageDown::default(), window, cx);
 2596    });
 2597    cx.assert_editor_state(
 2598        &r#"
 2599        one
 2600        two
 2601        three
 2602        four
 2603        five
 2604        six
 2605        seven
 2606        eight
 2607        nine
 2608        ˇten
 2609        ˇ"#
 2610        .unindent(),
 2611    );
 2612}
 2613
 2614#[gpui::test]
 2615async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2616    init_test(cx, |_| {});
 2617    let mut cx = EditorTestContext::new(cx).await;
 2618    cx.set_state("one «two threeˇ» four");
 2619    cx.update_editor(|editor, window, cx| {
 2620        editor.delete_to_beginning_of_line(
 2621            &DeleteToBeginningOfLine {
 2622                stop_at_indent: false,
 2623            },
 2624            window,
 2625            cx,
 2626        );
 2627        assert_eq!(editor.text(cx), " four");
 2628    });
 2629}
 2630
 2631#[gpui::test]
 2632async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2633    init_test(cx, |_| {});
 2634
 2635    let mut cx = EditorTestContext::new(cx).await;
 2636
 2637    // For an empty selection, the preceding word fragment is deleted.
 2638    // For non-empty selections, only selected characters are deleted.
 2639    cx.set_state("onˇe two t«hreˇ»e four");
 2640    cx.update_editor(|editor, window, cx| {
 2641        editor.delete_to_previous_word_start(
 2642            &DeleteToPreviousWordStart {
 2643                ignore_newlines: false,
 2644                ignore_brackets: false,
 2645            },
 2646            window,
 2647            cx,
 2648        );
 2649    });
 2650    cx.assert_editor_state("ˇe two tˇe four");
 2651
 2652    cx.set_state("e tˇwo te «fˇ»our");
 2653    cx.update_editor(|editor, window, cx| {
 2654        editor.delete_to_next_word_end(
 2655            &DeleteToNextWordEnd {
 2656                ignore_newlines: false,
 2657                ignore_brackets: false,
 2658            },
 2659            window,
 2660            cx,
 2661        );
 2662    });
 2663    cx.assert_editor_state("e tˇ te ˇour");
 2664}
 2665
 2666#[gpui::test]
 2667async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2668    init_test(cx, |_| {});
 2669
 2670    let mut cx = EditorTestContext::new(cx).await;
 2671
 2672    cx.set_state("here is some text    ˇwith a space");
 2673    cx.update_editor(|editor, window, cx| {
 2674        editor.delete_to_previous_word_start(
 2675            &DeleteToPreviousWordStart {
 2676                ignore_newlines: false,
 2677                ignore_brackets: true,
 2678            },
 2679            window,
 2680            cx,
 2681        );
 2682    });
 2683    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 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_previous_word_start(
 2689            &DeleteToPreviousWordStart {
 2690                ignore_newlines: false,
 2691                ignore_brackets: false,
 2692            },
 2693            window,
 2694            cx,
 2695        );
 2696    });
 2697    cx.assert_editor_state("here is some textˇwith a space");
 2698
 2699    cx.set_state("here is some textˇ    with a space");
 2700    cx.update_editor(|editor, window, cx| {
 2701        editor.delete_to_next_word_end(
 2702            &DeleteToNextWordEnd {
 2703                ignore_newlines: false,
 2704                ignore_brackets: true,
 2705            },
 2706            window,
 2707            cx,
 2708        );
 2709    });
 2710    // Same happens in the other direction.
 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: false,
 2718                ignore_brackets: false,
 2719            },
 2720            window,
 2721            cx,
 2722        );
 2723    });
 2724    cx.assert_editor_state("here is some textˇwith a space");
 2725
 2726    cx.set_state("here is some textˇ    with a space");
 2727    cx.update_editor(|editor, window, cx| {
 2728        editor.delete_to_next_word_end(
 2729            &DeleteToNextWordEnd {
 2730                ignore_newlines: true,
 2731                ignore_brackets: false,
 2732            },
 2733            window,
 2734            cx,
 2735        );
 2736    });
 2737    cx.assert_editor_state("here is some textˇwith a space");
 2738    cx.update_editor(|editor, window, cx| {
 2739        editor.delete_to_previous_word_start(
 2740            &DeleteToPreviousWordStart {
 2741                ignore_newlines: true,
 2742                ignore_brackets: false,
 2743            },
 2744            window,
 2745            cx,
 2746        );
 2747    });
 2748    cx.assert_editor_state("here is some ˇwith a space");
 2749    cx.update_editor(|editor, window, cx| {
 2750        editor.delete_to_previous_word_start(
 2751            &DeleteToPreviousWordStart {
 2752                ignore_newlines: true,
 2753                ignore_brackets: false,
 2754            },
 2755            window,
 2756            cx,
 2757        );
 2758    });
 2759    // Single whitespaces are removed with the word behind them.
 2760    cx.assert_editor_state("here is ˇwith a space");
 2761    cx.update_editor(|editor, window, cx| {
 2762        editor.delete_to_previous_word_start(
 2763            &DeleteToPreviousWordStart {
 2764                ignore_newlines: true,
 2765                ignore_brackets: false,
 2766            },
 2767            window,
 2768            cx,
 2769        );
 2770    });
 2771    cx.assert_editor_state("here ˇwith a space");
 2772    cx.update_editor(|editor, window, cx| {
 2773        editor.delete_to_previous_word_start(
 2774            &DeleteToPreviousWordStart {
 2775                ignore_newlines: true,
 2776                ignore_brackets: false,
 2777            },
 2778            window,
 2779            cx,
 2780        );
 2781    });
 2782    cx.assert_editor_state("ˇwith a space");
 2783    cx.update_editor(|editor, window, cx| {
 2784        editor.delete_to_previous_word_start(
 2785            &DeleteToPreviousWordStart {
 2786                ignore_newlines: true,
 2787                ignore_brackets: false,
 2788            },
 2789            window,
 2790            cx,
 2791        );
 2792    });
 2793    cx.assert_editor_state("ˇwith a space");
 2794    cx.update_editor(|editor, window, cx| {
 2795        editor.delete_to_next_word_end(
 2796            &DeleteToNextWordEnd {
 2797                ignore_newlines: true,
 2798                ignore_brackets: false,
 2799            },
 2800            window,
 2801            cx,
 2802        );
 2803    });
 2804    // Same happens in the other direction.
 2805    cx.assert_editor_state("ˇ a space");
 2806    cx.update_editor(|editor, window, cx| {
 2807        editor.delete_to_next_word_end(
 2808            &DeleteToNextWordEnd {
 2809                ignore_newlines: true,
 2810                ignore_brackets: false,
 2811            },
 2812            window,
 2813            cx,
 2814        );
 2815    });
 2816    cx.assert_editor_state("ˇ space");
 2817    cx.update_editor(|editor, window, cx| {
 2818        editor.delete_to_next_word_end(
 2819            &DeleteToNextWordEnd {
 2820                ignore_newlines: true,
 2821                ignore_brackets: false,
 2822            },
 2823            window,
 2824            cx,
 2825        );
 2826    });
 2827    cx.assert_editor_state("ˇ");
 2828    cx.update_editor(|editor, window, cx| {
 2829        editor.delete_to_next_word_end(
 2830            &DeleteToNextWordEnd {
 2831                ignore_newlines: true,
 2832                ignore_brackets: false,
 2833            },
 2834            window,
 2835            cx,
 2836        );
 2837    });
 2838    cx.assert_editor_state("ˇ");
 2839    cx.update_editor(|editor, window, cx| {
 2840        editor.delete_to_previous_word_start(
 2841            &DeleteToPreviousWordStart {
 2842                ignore_newlines: true,
 2843                ignore_brackets: false,
 2844            },
 2845            window,
 2846            cx,
 2847        );
 2848    });
 2849    cx.assert_editor_state("ˇ");
 2850}
 2851
 2852#[gpui::test]
 2853async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2854    init_test(cx, |_| {});
 2855
 2856    let language = Arc::new(
 2857        Language::new(
 2858            LanguageConfig {
 2859                brackets: BracketPairConfig {
 2860                    pairs: vec![
 2861                        BracketPair {
 2862                            start: "\"".to_string(),
 2863                            end: "\"".to_string(),
 2864                            close: true,
 2865                            surround: true,
 2866                            newline: false,
 2867                        },
 2868                        BracketPair {
 2869                            start: "(".to_string(),
 2870                            end: ")".to_string(),
 2871                            close: true,
 2872                            surround: true,
 2873                            newline: true,
 2874                        },
 2875                    ],
 2876                    ..BracketPairConfig::default()
 2877                },
 2878                ..LanguageConfig::default()
 2879            },
 2880            Some(tree_sitter_rust::LANGUAGE.into()),
 2881        )
 2882        .with_brackets_query(
 2883            r#"
 2884                ("(" @open ")" @close)
 2885                ("\"" @open "\"" @close)
 2886            "#,
 2887        )
 2888        .unwrap(),
 2889    );
 2890
 2891    let mut cx = EditorTestContext::new(cx).await;
 2892    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2893
 2894    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2895    cx.update_editor(|editor, window, cx| {
 2896        editor.delete_to_previous_word_start(
 2897            &DeleteToPreviousWordStart {
 2898                ignore_newlines: true,
 2899                ignore_brackets: false,
 2900            },
 2901            window,
 2902            cx,
 2903        );
 2904    });
 2905    // Deletion stops before brackets if asked to not ignore them.
 2906    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 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    // Deletion has to remove a single bracket and then stop again.
 2918    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2919
 2920    cx.update_editor(|editor, window, cx| {
 2921        editor.delete_to_previous_word_start(
 2922            &DeleteToPreviousWordStart {
 2923                ignore_newlines: true,
 2924                ignore_brackets: false,
 2925            },
 2926            window,
 2927            cx,
 2928        );
 2929    });
 2930    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2931
 2932    cx.update_editor(|editor, window, cx| {
 2933        editor.delete_to_previous_word_start(
 2934            &DeleteToPreviousWordStart {
 2935                ignore_newlines: true,
 2936                ignore_brackets: false,
 2937            },
 2938            window,
 2939            cx,
 2940        );
 2941    });
 2942    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2943
 2944    cx.update_editor(|editor, window, cx| {
 2945        editor.delete_to_previous_word_start(
 2946            &DeleteToPreviousWordStart {
 2947                ignore_newlines: true,
 2948                ignore_brackets: false,
 2949            },
 2950            window,
 2951            cx,
 2952        );
 2953    });
 2954    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 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    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2967    cx.assert_editor_state(r#"ˇ");"#);
 2968
 2969    cx.update_editor(|editor, window, cx| {
 2970        editor.delete_to_next_word_end(
 2971            &DeleteToNextWordEnd {
 2972                ignore_newlines: true,
 2973                ignore_brackets: false,
 2974            },
 2975            window,
 2976            cx,
 2977        );
 2978    });
 2979    cx.assert_editor_state(r#"ˇ"#);
 2980
 2981    cx.update_editor(|editor, window, cx| {
 2982        editor.delete_to_next_word_end(
 2983            &DeleteToNextWordEnd {
 2984                ignore_newlines: true,
 2985                ignore_brackets: false,
 2986            },
 2987            window,
 2988            cx,
 2989        );
 2990    });
 2991    cx.assert_editor_state(r#"ˇ"#);
 2992
 2993    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2994    cx.update_editor(|editor, window, cx| {
 2995        editor.delete_to_previous_word_start(
 2996            &DeleteToPreviousWordStart {
 2997                ignore_newlines: true,
 2998                ignore_brackets: true,
 2999            },
 3000            window,
 3001            cx,
 3002        );
 3003    });
 3004    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 3005}
 3006
 3007#[gpui::test]
 3008fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 3009    init_test(cx, |_| {});
 3010
 3011    let editor = cx.add_window(|window, cx| {
 3012        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3013        build_editor(buffer, window, cx)
 3014    });
 3015    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3016        ignore_newlines: false,
 3017        ignore_brackets: false,
 3018    };
 3019    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3020        ignore_newlines: true,
 3021        ignore_brackets: false,
 3022    };
 3023
 3024    _ = editor.update(cx, |editor, window, cx| {
 3025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3026            s.select_display_ranges([
 3027                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3028            ])
 3029        });
 3030        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3031        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3032        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3033        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3034        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3035        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3036        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3037        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3038        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3039        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3040        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3041        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3042    });
 3043}
 3044
 3045#[gpui::test]
 3046fn test_delete_to_previous_subword_start_or_newline(cx: &mut TestAppContext) {
 3047    init_test(cx, |_| {});
 3048
 3049    let editor = cx.add_window(|window, cx| {
 3050        let buffer = MultiBuffer::build_simple("fooBar\n\nbazQux", cx);
 3051        build_editor(buffer, window, cx)
 3052    });
 3053    let del_to_prev_sub_word_start = DeleteToPreviousSubwordStart {
 3054        ignore_newlines: false,
 3055        ignore_brackets: false,
 3056    };
 3057    let del_to_prev_sub_word_start_ignore_newlines = DeleteToPreviousSubwordStart {
 3058        ignore_newlines: true,
 3059        ignore_brackets: false,
 3060    };
 3061
 3062    _ = editor.update(cx, |editor, window, cx| {
 3063        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3064            s.select_display_ranges([
 3065                DisplayPoint::new(DisplayRow(2), 6)..DisplayPoint::new(DisplayRow(2), 6)
 3066            ])
 3067        });
 3068        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3069        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\nbaz");
 3070        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3071        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\n");
 3072        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3073        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n");
 3074        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3075        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar");
 3076        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3077        assert_eq!(editor.buffer.read(cx).read(cx).text(), "foo");
 3078        editor.delete_to_previous_subword_start(
 3079            &del_to_prev_sub_word_start_ignore_newlines,
 3080            window,
 3081            cx,
 3082        );
 3083        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3084    });
 3085}
 3086
 3087#[gpui::test]
 3088fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3089    init_test(cx, |_| {});
 3090
 3091    let editor = cx.add_window(|window, cx| {
 3092        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3093        build_editor(buffer, window, cx)
 3094    });
 3095    let del_to_next_word_end = DeleteToNextWordEnd {
 3096        ignore_newlines: false,
 3097        ignore_brackets: false,
 3098    };
 3099    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3100        ignore_newlines: true,
 3101        ignore_brackets: false,
 3102    };
 3103
 3104    _ = editor.update(cx, |editor, window, cx| {
 3105        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3106            s.select_display_ranges([
 3107                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3108            ])
 3109        });
 3110        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3111        assert_eq!(
 3112            editor.buffer.read(cx).read(cx).text(),
 3113            "one\n   two\nthree\n   four"
 3114        );
 3115        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3116        assert_eq!(
 3117            editor.buffer.read(cx).read(cx).text(),
 3118            "\n   two\nthree\n   four"
 3119        );
 3120        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3121        assert_eq!(
 3122            editor.buffer.read(cx).read(cx).text(),
 3123            "two\nthree\n   four"
 3124        );
 3125        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3126        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3127        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3128        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3129        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3130        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3131        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3132        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3133    });
 3134}
 3135
 3136#[gpui::test]
 3137fn test_delete_to_next_subword_end_or_newline(cx: &mut TestAppContext) {
 3138    init_test(cx, |_| {});
 3139
 3140    let editor = cx.add_window(|window, cx| {
 3141        let buffer = MultiBuffer::build_simple("\nfooBar\n   bazQux", cx);
 3142        build_editor(buffer, window, cx)
 3143    });
 3144    let del_to_next_subword_end = DeleteToNextSubwordEnd {
 3145        ignore_newlines: false,
 3146        ignore_brackets: false,
 3147    };
 3148    let del_to_next_subword_end_ignore_newlines = DeleteToNextSubwordEnd {
 3149        ignore_newlines: true,
 3150        ignore_brackets: false,
 3151    };
 3152
 3153    _ = editor.update(cx, |editor, window, cx| {
 3154        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3155            s.select_display_ranges([
 3156                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3157            ])
 3158        });
 3159        // Delete "\n" (empty line)
 3160        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3161        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n   bazQux");
 3162        // Delete "foo" (subword boundary)
 3163        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3164        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Bar\n   bazQux");
 3165        // Delete "Bar"
 3166        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3167        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   bazQux");
 3168        // Delete "\n   " (newline + leading whitespace)
 3169        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3170        assert_eq!(editor.buffer.read(cx).read(cx).text(), "bazQux");
 3171        // Delete "baz" (subword boundary)
 3172        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3173        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Qux");
 3174        // With ignore_newlines, delete "Qux"
 3175        editor.delete_to_next_subword_end(&del_to_next_subword_end_ignore_newlines, window, cx);
 3176        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3177    });
 3178}
 3179
 3180#[gpui::test]
 3181fn test_newline(cx: &mut TestAppContext) {
 3182    init_test(cx, |_| {});
 3183
 3184    let editor = cx.add_window(|window, cx| {
 3185        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3186        build_editor(buffer, window, cx)
 3187    });
 3188
 3189    _ = editor.update(cx, |editor, window, cx| {
 3190        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3191            s.select_display_ranges([
 3192                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3193                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3194                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3195            ])
 3196        });
 3197
 3198        editor.newline(&Newline, window, cx);
 3199        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3200    });
 3201}
 3202
 3203#[gpui::test]
 3204async fn test_newline_yaml(cx: &mut TestAppContext) {
 3205    init_test(cx, |_| {});
 3206
 3207    let mut cx = EditorTestContext::new(cx).await;
 3208    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3209    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3210
 3211    // Object (between 2 fields)
 3212    cx.set_state(indoc! {"
 3213    test:ˇ
 3214    hello: bye"});
 3215    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3216    cx.assert_editor_state(indoc! {"
 3217    test:
 3218        ˇ
 3219    hello: bye"});
 3220
 3221    // Object (first and single line)
 3222    cx.set_state(indoc! {"
 3223    test:ˇ"});
 3224    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3225    cx.assert_editor_state(indoc! {"
 3226    test:
 3227        ˇ"});
 3228
 3229    // Array with objects (after first element)
 3230    cx.set_state(indoc! {"
 3231    test:
 3232        - foo: barˇ"});
 3233    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3234    cx.assert_editor_state(indoc! {"
 3235    test:
 3236        - foo: bar
 3237        ˇ"});
 3238
 3239    // Array with objects and comment
 3240    cx.set_state(indoc! {"
 3241    test:
 3242        - foo: bar
 3243        - bar: # testˇ"});
 3244    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3245    cx.assert_editor_state(indoc! {"
 3246    test:
 3247        - foo: bar
 3248        - bar: # test
 3249            ˇ"});
 3250
 3251    // Array with objects (after second element)
 3252    cx.set_state(indoc! {"
 3253    test:
 3254        - foo: bar
 3255        - bar: fooˇ"});
 3256    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3257    cx.assert_editor_state(indoc! {"
 3258    test:
 3259        - foo: bar
 3260        - bar: foo
 3261        ˇ"});
 3262
 3263    // Array with strings (after first element)
 3264    cx.set_state(indoc! {"
 3265    test:
 3266        - fooˇ"});
 3267    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3268    cx.assert_editor_state(indoc! {"
 3269    test:
 3270        - foo
 3271        ˇ"});
 3272}
 3273
 3274#[gpui::test]
 3275fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3276    init_test(cx, |_| {});
 3277
 3278    let editor = cx.add_window(|window, cx| {
 3279        let buffer = MultiBuffer::build_simple(
 3280            "
 3281                a
 3282                b(
 3283                    X
 3284                )
 3285                c(
 3286                    X
 3287                )
 3288            "
 3289            .unindent()
 3290            .as_str(),
 3291            cx,
 3292        );
 3293        let mut editor = build_editor(buffer, window, cx);
 3294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3295            s.select_ranges([
 3296                Point::new(2, 4)..Point::new(2, 5),
 3297                Point::new(5, 4)..Point::new(5, 5),
 3298            ])
 3299        });
 3300        editor
 3301    });
 3302
 3303    _ = editor.update(cx, |editor, window, cx| {
 3304        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3305        editor.buffer.update(cx, |buffer, cx| {
 3306            buffer.edit(
 3307                [
 3308                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3309                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3310                ],
 3311                None,
 3312                cx,
 3313            );
 3314            assert_eq!(
 3315                buffer.read(cx).text(),
 3316                "
 3317                    a
 3318                    b()
 3319                    c()
 3320                "
 3321                .unindent()
 3322            );
 3323        });
 3324        assert_eq!(
 3325            editor.selections.ranges(&editor.display_snapshot(cx)),
 3326            &[
 3327                Point::new(1, 2)..Point::new(1, 2),
 3328                Point::new(2, 2)..Point::new(2, 2),
 3329            ],
 3330        );
 3331
 3332        editor.newline(&Newline, window, cx);
 3333        assert_eq!(
 3334            editor.text(cx),
 3335            "
 3336                a
 3337                b(
 3338                )
 3339                c(
 3340                )
 3341            "
 3342            .unindent()
 3343        );
 3344
 3345        // The selections are moved after the inserted newlines
 3346        assert_eq!(
 3347            editor.selections.ranges(&editor.display_snapshot(cx)),
 3348            &[
 3349                Point::new(2, 0)..Point::new(2, 0),
 3350                Point::new(4, 0)..Point::new(4, 0),
 3351            ],
 3352        );
 3353    });
 3354}
 3355
 3356#[gpui::test]
 3357async fn test_newline_above(cx: &mut TestAppContext) {
 3358    init_test(cx, |settings| {
 3359        settings.defaults.tab_size = NonZeroU32::new(4)
 3360    });
 3361
 3362    let language = Arc::new(
 3363        Language::new(
 3364            LanguageConfig::default(),
 3365            Some(tree_sitter_rust::LANGUAGE.into()),
 3366        )
 3367        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3368        .unwrap(),
 3369    );
 3370
 3371    let mut cx = EditorTestContext::new(cx).await;
 3372    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3373    cx.set_state(indoc! {"
 3374        const a: ˇA = (
 3375 3376                «const_functionˇ»(ˇ),
 3377                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3378 3379        ˇ);ˇ
 3380    "});
 3381
 3382    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3383    cx.assert_editor_state(indoc! {"
 3384        ˇ
 3385        const a: A = (
 3386            ˇ
 3387            (
 3388                ˇ
 3389                ˇ
 3390                const_function(),
 3391                ˇ
 3392                ˇ
 3393                ˇ
 3394                ˇ
 3395                something_else,
 3396                ˇ
 3397            )
 3398            ˇ
 3399            ˇ
 3400        );
 3401    "});
 3402}
 3403
 3404#[gpui::test]
 3405async fn test_newline_below(cx: &mut TestAppContext) {
 3406    init_test(cx, |settings| {
 3407        settings.defaults.tab_size = NonZeroU32::new(4)
 3408    });
 3409
 3410    let language = Arc::new(
 3411        Language::new(
 3412            LanguageConfig::default(),
 3413            Some(tree_sitter_rust::LANGUAGE.into()),
 3414        )
 3415        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3416        .unwrap(),
 3417    );
 3418
 3419    let mut cx = EditorTestContext::new(cx).await;
 3420    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3421    cx.set_state(indoc! {"
 3422        const a: ˇA = (
 3423 3424                «const_functionˇ»(ˇ),
 3425                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3426 3427        ˇ);ˇ
 3428    "});
 3429
 3430    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3431    cx.assert_editor_state(indoc! {"
 3432        const a: A = (
 3433            ˇ
 3434            (
 3435                ˇ
 3436                const_function(),
 3437                ˇ
 3438                ˇ
 3439                something_else,
 3440                ˇ
 3441                ˇ
 3442                ˇ
 3443                ˇ
 3444            )
 3445            ˇ
 3446        );
 3447        ˇ
 3448        ˇ
 3449    "});
 3450}
 3451
 3452#[gpui::test]
 3453async fn test_newline_comments(cx: &mut TestAppContext) {
 3454    init_test(cx, |settings| {
 3455        settings.defaults.tab_size = NonZeroU32::new(4)
 3456    });
 3457
 3458    let language = Arc::new(Language::new(
 3459        LanguageConfig {
 3460            line_comments: vec!["// ".into()],
 3461            ..LanguageConfig::default()
 3462        },
 3463        None,
 3464    ));
 3465    {
 3466        let mut cx = EditorTestContext::new(cx).await;
 3467        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3468        cx.set_state(indoc! {"
 3469        // Fooˇ
 3470    "});
 3471
 3472        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3473        cx.assert_editor_state(indoc! {"
 3474        // Foo
 3475        // ˇ
 3476    "});
 3477        // Ensure that we add comment prefix when existing line contains space
 3478        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3479        cx.assert_editor_state(
 3480            indoc! {"
 3481        // Foo
 3482        //s
 3483        // ˇ
 3484    "}
 3485            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3486            .as_str(),
 3487        );
 3488        // Ensure that we add comment prefix when existing line does not contain space
 3489        cx.set_state(indoc! {"
 3490        // Foo
 3491        //ˇ
 3492    "});
 3493        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3494        cx.assert_editor_state(indoc! {"
 3495        // Foo
 3496        //
 3497        // ˇ
 3498    "});
 3499        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3500        cx.set_state(indoc! {"
 3501        ˇ// Foo
 3502    "});
 3503        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3504        cx.assert_editor_state(indoc! {"
 3505
 3506        ˇ// Foo
 3507    "});
 3508    }
 3509    // Ensure that comment continuations can be disabled.
 3510    update_test_language_settings(cx, |settings| {
 3511        settings.defaults.extend_comment_on_newline = Some(false);
 3512    });
 3513    let mut cx = EditorTestContext::new(cx).await;
 3514    cx.set_state(indoc! {"
 3515        // Fooˇ
 3516    "});
 3517    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3518    cx.assert_editor_state(indoc! {"
 3519        // Foo
 3520        ˇ
 3521    "});
 3522}
 3523
 3524#[gpui::test]
 3525async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3526    init_test(cx, |settings| {
 3527        settings.defaults.tab_size = NonZeroU32::new(4)
 3528    });
 3529
 3530    let language = Arc::new(Language::new(
 3531        LanguageConfig {
 3532            line_comments: vec!["// ".into(), "/// ".into()],
 3533            ..LanguageConfig::default()
 3534        },
 3535        None,
 3536    ));
 3537    {
 3538        let mut cx = EditorTestContext::new(cx).await;
 3539        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3540        cx.set_state(indoc! {"
 3541        //ˇ
 3542    "});
 3543        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3544        cx.assert_editor_state(indoc! {"
 3545        //
 3546        // ˇ
 3547    "});
 3548
 3549        cx.set_state(indoc! {"
 3550        ///ˇ
 3551    "});
 3552        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3553        cx.assert_editor_state(indoc! {"
 3554        ///
 3555        /// ˇ
 3556    "});
 3557    }
 3558}
 3559
 3560#[gpui::test]
 3561async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3562    init_test(cx, |settings| {
 3563        settings.defaults.tab_size = NonZeroU32::new(4)
 3564    });
 3565
 3566    let language = Arc::new(
 3567        Language::new(
 3568            LanguageConfig {
 3569                documentation_comment: Some(language::BlockCommentConfig {
 3570                    start: "/**".into(),
 3571                    end: "*/".into(),
 3572                    prefix: "* ".into(),
 3573                    tab_size: 1,
 3574                }),
 3575
 3576                ..LanguageConfig::default()
 3577            },
 3578            Some(tree_sitter_rust::LANGUAGE.into()),
 3579        )
 3580        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3581        .unwrap(),
 3582    );
 3583
 3584    {
 3585        let mut cx = EditorTestContext::new(cx).await;
 3586        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3587        cx.set_state(indoc! {"
 3588        /**ˇ
 3589    "});
 3590
 3591        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3592        cx.assert_editor_state(indoc! {"
 3593        /**
 3594         * ˇ
 3595    "});
 3596        // Ensure that if cursor is before the comment start,
 3597        // we do not actually insert a comment prefix.
 3598        cx.set_state(indoc! {"
 3599        ˇ/**
 3600    "});
 3601        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3602        cx.assert_editor_state(indoc! {"
 3603
 3604        ˇ/**
 3605    "});
 3606        // Ensure that if cursor is between it doesn't add comment prefix.
 3607        cx.set_state(indoc! {"
 3608        /*ˇ*
 3609    "});
 3610        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3611        cx.assert_editor_state(indoc! {"
 3612        /*
 3613        ˇ*
 3614    "});
 3615        // Ensure that if suffix exists on same line after cursor it adds new line.
 3616        cx.set_state(indoc! {"
 3617        /**ˇ*/
 3618    "});
 3619        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3620        cx.assert_editor_state(indoc! {"
 3621        /**
 3622         * ˇ
 3623         */
 3624    "});
 3625        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3626        cx.set_state(indoc! {"
 3627        /**ˇ */
 3628    "});
 3629        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3630        cx.assert_editor_state(indoc! {"
 3631        /**
 3632         * ˇ
 3633         */
 3634    "});
 3635        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3636        cx.set_state(indoc! {"
 3637        /** ˇ*/
 3638    "});
 3639        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3640        cx.assert_editor_state(
 3641            indoc! {"
 3642        /**s
 3643         * ˇ
 3644         */
 3645    "}
 3646            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3647            .as_str(),
 3648        );
 3649        // Ensure that delimiter space is preserved when newline on already
 3650        // spaced delimiter.
 3651        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3652        cx.assert_editor_state(
 3653            indoc! {"
 3654        /**s
 3655         *s
 3656         * ˇ
 3657         */
 3658    "}
 3659            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3660            .as_str(),
 3661        );
 3662        // Ensure that delimiter space is preserved when space is not
 3663        // on existing delimiter.
 3664        cx.set_state(indoc! {"
 3665        /**
 3666 3667         */
 3668    "});
 3669        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3670        cx.assert_editor_state(indoc! {"
 3671        /**
 3672         *
 3673         * ˇ
 3674         */
 3675    "});
 3676        // Ensure that if suffix exists on same line after cursor it
 3677        // doesn't add extra new line if prefix is not on same line.
 3678        cx.set_state(indoc! {"
 3679        /**
 3680        ˇ*/
 3681    "});
 3682        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3683        cx.assert_editor_state(indoc! {"
 3684        /**
 3685
 3686        ˇ*/
 3687    "});
 3688        // Ensure that it detects suffix after existing prefix.
 3689        cx.set_state(indoc! {"
 3690        /**ˇ/
 3691    "});
 3692        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3693        cx.assert_editor_state(indoc! {"
 3694        /**
 3695        ˇ/
 3696    "});
 3697        // Ensure that if suffix exists on same line before
 3698        // cursor it does not add comment prefix.
 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        // Ensure that if suffix exists on same line before
 3708        // cursor it does not add comment prefix.
 3709        cx.set_state(indoc! {"
 3710        /**
 3711         *
 3712         */ˇ
 3713    "});
 3714        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3715        cx.assert_editor_state(indoc! {"
 3716        /**
 3717         *
 3718         */
 3719         ˇ
 3720    "});
 3721
 3722        // Ensure that inline comment followed by code
 3723        // doesn't add comment prefix on newline
 3724        cx.set_state(indoc! {"
 3725        /** */ textˇ
 3726    "});
 3727        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3728        cx.assert_editor_state(indoc! {"
 3729        /** */ text
 3730        ˇ
 3731    "});
 3732
 3733        // Ensure that text after comment end tag
 3734        // doesn't add comment prefix on newline
 3735        cx.set_state(indoc! {"
 3736        /**
 3737         *
 3738         */ˇtext
 3739    "});
 3740        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3741        cx.assert_editor_state(indoc! {"
 3742        /**
 3743         *
 3744         */
 3745         ˇtext
 3746    "});
 3747
 3748        // Ensure if not comment block it doesn't
 3749        // add comment prefix on newline
 3750        cx.set_state(indoc! {"
 3751        * textˇ
 3752    "});
 3753        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3754        cx.assert_editor_state(indoc! {"
 3755        * text
 3756        ˇ
 3757    "});
 3758    }
 3759    // Ensure that comment continuations can be disabled.
 3760    update_test_language_settings(cx, |settings| {
 3761        settings.defaults.extend_comment_on_newline = Some(false);
 3762    });
 3763    let mut cx = EditorTestContext::new(cx).await;
 3764    cx.set_state(indoc! {"
 3765        /**ˇ
 3766    "});
 3767    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3768    cx.assert_editor_state(indoc! {"
 3769        /**
 3770        ˇ
 3771    "});
 3772}
 3773
 3774#[gpui::test]
 3775async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3776    init_test(cx, |settings| {
 3777        settings.defaults.tab_size = NonZeroU32::new(4)
 3778    });
 3779
 3780    let lua_language = Arc::new(Language::new(
 3781        LanguageConfig {
 3782            line_comments: vec!["--".into()],
 3783            block_comment: Some(language::BlockCommentConfig {
 3784                start: "--[[".into(),
 3785                prefix: "".into(),
 3786                end: "]]".into(),
 3787                tab_size: 0,
 3788            }),
 3789            ..LanguageConfig::default()
 3790        },
 3791        None,
 3792    ));
 3793
 3794    let mut cx = EditorTestContext::new(cx).await;
 3795    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3796
 3797    // Line with line comment should extend
 3798    cx.set_state(indoc! {"
 3799        --ˇ
 3800    "});
 3801    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3802    cx.assert_editor_state(indoc! {"
 3803        --
 3804        --ˇ
 3805    "});
 3806
 3807    // Line with block comment that matches line comment should not extend
 3808    cx.set_state(indoc! {"
 3809        --[[ˇ
 3810    "});
 3811    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3812    cx.assert_editor_state(indoc! {"
 3813        --[[
 3814        ˇ
 3815    "});
 3816}
 3817
 3818#[gpui::test]
 3819fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3820    init_test(cx, |_| {});
 3821
 3822    let editor = cx.add_window(|window, cx| {
 3823        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3824        let mut editor = build_editor(buffer, window, cx);
 3825        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3826            s.select_ranges([
 3827                MultiBufferOffset(3)..MultiBufferOffset(4),
 3828                MultiBufferOffset(11)..MultiBufferOffset(12),
 3829                MultiBufferOffset(19)..MultiBufferOffset(20),
 3830            ])
 3831        });
 3832        editor
 3833    });
 3834
 3835    _ = editor.update(cx, |editor, window, cx| {
 3836        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3837        editor.buffer.update(cx, |buffer, cx| {
 3838            buffer.edit(
 3839                [
 3840                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 3841                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 3842                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 3843                ],
 3844                None,
 3845                cx,
 3846            );
 3847            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3848        });
 3849        assert_eq!(
 3850            editor.selections.ranges(&editor.display_snapshot(cx)),
 3851            &[
 3852                MultiBufferOffset(2)..MultiBufferOffset(2),
 3853                MultiBufferOffset(7)..MultiBufferOffset(7),
 3854                MultiBufferOffset(12)..MultiBufferOffset(12)
 3855            ],
 3856        );
 3857
 3858        editor.insert("Z", window, cx);
 3859        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3860
 3861        // The selections are moved after the inserted characters
 3862        assert_eq!(
 3863            editor.selections.ranges(&editor.display_snapshot(cx)),
 3864            &[
 3865                MultiBufferOffset(3)..MultiBufferOffset(3),
 3866                MultiBufferOffset(9)..MultiBufferOffset(9),
 3867                MultiBufferOffset(15)..MultiBufferOffset(15)
 3868            ],
 3869        );
 3870    });
 3871}
 3872
 3873#[gpui::test]
 3874async fn test_tab(cx: &mut TestAppContext) {
 3875    init_test(cx, |settings| {
 3876        settings.defaults.tab_size = NonZeroU32::new(3)
 3877    });
 3878
 3879    let mut cx = EditorTestContext::new(cx).await;
 3880    cx.set_state(indoc! {"
 3881        ˇabˇc
 3882        ˇ🏀ˇ🏀ˇefg
 3883 3884    "});
 3885    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3886    cx.assert_editor_state(indoc! {"
 3887           ˇab ˇc
 3888           ˇ🏀  ˇ🏀  ˇefg
 3889        d  ˇ
 3890    "});
 3891
 3892    cx.set_state(indoc! {"
 3893        a
 3894        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3895    "});
 3896    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3897    cx.assert_editor_state(indoc! {"
 3898        a
 3899           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3900    "});
 3901}
 3902
 3903#[gpui::test]
 3904async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3905    init_test(cx, |_| {});
 3906
 3907    let mut cx = EditorTestContext::new(cx).await;
 3908    let language = Arc::new(
 3909        Language::new(
 3910            LanguageConfig::default(),
 3911            Some(tree_sitter_rust::LANGUAGE.into()),
 3912        )
 3913        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3914        .unwrap(),
 3915    );
 3916    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3917
 3918    // test when all cursors are not at suggested indent
 3919    // then simply move to their suggested indent location
 3920    cx.set_state(indoc! {"
 3921        const a: B = (
 3922            c(
 3923        ˇ
 3924        ˇ    )
 3925        );
 3926    "});
 3927    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3928    cx.assert_editor_state(indoc! {"
 3929        const a: B = (
 3930            c(
 3931                ˇ
 3932            ˇ)
 3933        );
 3934    "});
 3935
 3936    // test cursor already at suggested indent not moving when
 3937    // other cursors are yet to reach their suggested indents
 3938    cx.set_state(indoc! {"
 3939        ˇ
 3940        const a: B = (
 3941            c(
 3942                d(
 3943        ˇ
 3944                )
 3945        ˇ
 3946        ˇ    )
 3947        );
 3948    "});
 3949    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3950    cx.assert_editor_state(indoc! {"
 3951        ˇ
 3952        const a: B = (
 3953            c(
 3954                d(
 3955                    ˇ
 3956                )
 3957                ˇ
 3958            ˇ)
 3959        );
 3960    "});
 3961    // test when all cursors are at suggested indent then tab is inserted
 3962    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3963    cx.assert_editor_state(indoc! {"
 3964            ˇ
 3965        const a: B = (
 3966            c(
 3967                d(
 3968                        ˇ
 3969                )
 3970                    ˇ
 3971                ˇ)
 3972        );
 3973    "});
 3974
 3975    // test when current indent is less than suggested indent,
 3976    // we adjust line to match suggested indent and move cursor to it
 3977    //
 3978    // when no other cursor is at word boundary, all of them should move
 3979    cx.set_state(indoc! {"
 3980        const a: B = (
 3981            c(
 3982                d(
 3983        ˇ
 3984        ˇ   )
 3985        ˇ   )
 3986        );
 3987    "});
 3988    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3989    cx.assert_editor_state(indoc! {"
 3990        const a: B = (
 3991            c(
 3992                d(
 3993                    ˇ
 3994                ˇ)
 3995            ˇ)
 3996        );
 3997    "});
 3998
 3999    // test when current indent is less than suggested indent,
 4000    // we adjust line to match suggested indent and move cursor to it
 4001    //
 4002    // when some other cursor is at word boundary, it should not move
 4003    cx.set_state(indoc! {"
 4004        const a: B = (
 4005            c(
 4006                d(
 4007        ˇ
 4008        ˇ   )
 4009           ˇ)
 4010        );
 4011    "});
 4012    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4013    cx.assert_editor_state(indoc! {"
 4014        const a: B = (
 4015            c(
 4016                d(
 4017                    ˇ
 4018                ˇ)
 4019            ˇ)
 4020        );
 4021    "});
 4022
 4023    // test when current indent is more than suggested indent,
 4024    // we just move cursor to current indent instead of suggested indent
 4025    //
 4026    // when no other cursor is at word boundary, all of them should move
 4027    cx.set_state(indoc! {"
 4028        const a: B = (
 4029            c(
 4030                d(
 4031        ˇ
 4032        ˇ                )
 4033        ˇ   )
 4034        );
 4035    "});
 4036    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4037    cx.assert_editor_state(indoc! {"
 4038        const a: B = (
 4039            c(
 4040                d(
 4041                    ˇ
 4042                        ˇ)
 4043            ˇ)
 4044        );
 4045    "});
 4046    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4047    cx.assert_editor_state(indoc! {"
 4048        const a: B = (
 4049            c(
 4050                d(
 4051                        ˇ
 4052                            ˇ)
 4053                ˇ)
 4054        );
 4055    "});
 4056
 4057    // test when current indent is more than suggested indent,
 4058    // we just move cursor to current indent instead of suggested indent
 4059    //
 4060    // when some other cursor is at word boundary, it doesn't move
 4061    cx.set_state(indoc! {"
 4062        const a: B = (
 4063            c(
 4064                d(
 4065        ˇ
 4066        ˇ                )
 4067            ˇ)
 4068        );
 4069    "});
 4070    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4071    cx.assert_editor_state(indoc! {"
 4072        const a: B = (
 4073            c(
 4074                d(
 4075                    ˇ
 4076                        ˇ)
 4077            ˇ)
 4078        );
 4079    "});
 4080
 4081    // handle auto-indent when there are multiple cursors on the same line
 4082    cx.set_state(indoc! {"
 4083        const a: B = (
 4084            c(
 4085        ˇ    ˇ
 4086        ˇ    )
 4087        );
 4088    "});
 4089    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4090    cx.assert_editor_state(indoc! {"
 4091        const a: B = (
 4092            c(
 4093                ˇ
 4094            ˇ)
 4095        );
 4096    "});
 4097}
 4098
 4099#[gpui::test]
 4100async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4101    init_test(cx, |settings| {
 4102        settings.defaults.tab_size = NonZeroU32::new(3)
 4103    });
 4104
 4105    let mut cx = EditorTestContext::new(cx).await;
 4106    cx.set_state(indoc! {"
 4107         ˇ
 4108        \t ˇ
 4109        \t  ˇ
 4110        \t   ˇ
 4111         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4112    "});
 4113
 4114    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4115    cx.assert_editor_state(indoc! {"
 4116           ˇ
 4117        \t   ˇ
 4118        \t   ˇ
 4119        \t      ˇ
 4120         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4121    "});
 4122}
 4123
 4124#[gpui::test]
 4125async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4126    init_test(cx, |settings| {
 4127        settings.defaults.tab_size = NonZeroU32::new(4)
 4128    });
 4129
 4130    let language = Arc::new(
 4131        Language::new(
 4132            LanguageConfig::default(),
 4133            Some(tree_sitter_rust::LANGUAGE.into()),
 4134        )
 4135        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4136        .unwrap(),
 4137    );
 4138
 4139    let mut cx = EditorTestContext::new(cx).await;
 4140    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4141    cx.set_state(indoc! {"
 4142        fn a() {
 4143            if b {
 4144        \t ˇc
 4145            }
 4146        }
 4147    "});
 4148
 4149    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4150    cx.assert_editor_state(indoc! {"
 4151        fn a() {
 4152            if b {
 4153                ˇc
 4154            }
 4155        }
 4156    "});
 4157}
 4158
 4159#[gpui::test]
 4160async fn test_indent_outdent(cx: &mut TestAppContext) {
 4161    init_test(cx, |settings| {
 4162        settings.defaults.tab_size = NonZeroU32::new(4);
 4163    });
 4164
 4165    let mut cx = EditorTestContext::new(cx).await;
 4166
 4167    cx.set_state(indoc! {"
 4168          «oneˇ» «twoˇ»
 4169        three
 4170         four
 4171    "});
 4172    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4173    cx.assert_editor_state(indoc! {"
 4174            «oneˇ» «twoˇ»
 4175        three
 4176         four
 4177    "});
 4178
 4179    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4180    cx.assert_editor_state(indoc! {"
 4181        «oneˇ» «twoˇ»
 4182        three
 4183         four
 4184    "});
 4185
 4186    // select across line ending
 4187    cx.set_state(indoc! {"
 4188        one two
 4189        t«hree
 4190        ˇ» four
 4191    "});
 4192    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4193    cx.assert_editor_state(indoc! {"
 4194        one two
 4195            t«hree
 4196        ˇ» four
 4197    "});
 4198
 4199    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4200    cx.assert_editor_state(indoc! {"
 4201        one two
 4202        t«hree
 4203        ˇ» four
 4204    "});
 4205
 4206    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4207    cx.set_state(indoc! {"
 4208        one two
 4209        ˇthree
 4210            four
 4211    "});
 4212    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4213    cx.assert_editor_state(indoc! {"
 4214        one two
 4215            ˇthree
 4216            four
 4217    "});
 4218
 4219    cx.set_state(indoc! {"
 4220        one two
 4221        ˇ    three
 4222            four
 4223    "});
 4224    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4225    cx.assert_editor_state(indoc! {"
 4226        one two
 4227        ˇthree
 4228            four
 4229    "});
 4230}
 4231
 4232#[gpui::test]
 4233async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4234    // This is a regression test for issue #33761
 4235    init_test(cx, |_| {});
 4236
 4237    let mut cx = EditorTestContext::new(cx).await;
 4238    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4239    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4240
 4241    cx.set_state(
 4242        r#"ˇ#     ingress:
 4243ˇ#         api:
 4244ˇ#             enabled: false
 4245ˇ#             pathType: Prefix
 4246ˇ#           console:
 4247ˇ#               enabled: false
 4248ˇ#               pathType: Prefix
 4249"#,
 4250    );
 4251
 4252    // Press tab to indent all lines
 4253    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4254
 4255    cx.assert_editor_state(
 4256        r#"    ˇ#     ingress:
 4257    ˇ#         api:
 4258    ˇ#             enabled: false
 4259    ˇ#             pathType: Prefix
 4260    ˇ#           console:
 4261    ˇ#               enabled: false
 4262    ˇ#               pathType: Prefix
 4263"#,
 4264    );
 4265}
 4266
 4267#[gpui::test]
 4268async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4269    // This is a test to make sure our fix for issue #33761 didn't break anything
 4270    init_test(cx, |_| {});
 4271
 4272    let mut cx = EditorTestContext::new(cx).await;
 4273    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4274    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4275
 4276    cx.set_state(
 4277        r#"ˇingress:
 4278ˇ  api:
 4279ˇ    enabled: false
 4280ˇ    pathType: Prefix
 4281"#,
 4282    );
 4283
 4284    // Press tab to indent all lines
 4285    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4286
 4287    cx.assert_editor_state(
 4288        r#"ˇingress:
 4289    ˇapi:
 4290        ˇenabled: false
 4291        ˇpathType: Prefix
 4292"#,
 4293    );
 4294}
 4295
 4296#[gpui::test]
 4297async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4298    init_test(cx, |settings| {
 4299        settings.defaults.hard_tabs = Some(true);
 4300    });
 4301
 4302    let mut cx = EditorTestContext::new(cx).await;
 4303
 4304    // select two ranges on one line
 4305    cx.set_state(indoc! {"
 4306        «oneˇ» «twoˇ»
 4307        three
 4308        four
 4309    "});
 4310    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4311    cx.assert_editor_state(indoc! {"
 4312        \t«oneˇ» «twoˇ»
 4313        three
 4314        four
 4315    "});
 4316    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4317    cx.assert_editor_state(indoc! {"
 4318        \t\t«oneˇ» «twoˇ»
 4319        three
 4320        four
 4321    "});
 4322    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4323    cx.assert_editor_state(indoc! {"
 4324        \t«oneˇ» «twoˇ»
 4325        three
 4326        four
 4327    "});
 4328    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4329    cx.assert_editor_state(indoc! {"
 4330        «oneˇ» «twoˇ»
 4331        three
 4332        four
 4333    "});
 4334
 4335    // select across a line ending
 4336    cx.set_state(indoc! {"
 4337        one two
 4338        t«hree
 4339        ˇ»four
 4340    "});
 4341    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4342    cx.assert_editor_state(indoc! {"
 4343        one two
 4344        \tt«hree
 4345        ˇ»four
 4346    "});
 4347    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4348    cx.assert_editor_state(indoc! {"
 4349        one two
 4350        \t\tt«hree
 4351        ˇ»four
 4352    "});
 4353    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4354    cx.assert_editor_state(indoc! {"
 4355        one two
 4356        \tt«hree
 4357        ˇ»four
 4358    "});
 4359    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4360    cx.assert_editor_state(indoc! {"
 4361        one two
 4362        t«hree
 4363        ˇ»four
 4364    "});
 4365
 4366    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4367    cx.set_state(indoc! {"
 4368        one two
 4369        ˇthree
 4370        four
 4371    "});
 4372    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4373    cx.assert_editor_state(indoc! {"
 4374        one two
 4375        ˇthree
 4376        four
 4377    "});
 4378    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4379    cx.assert_editor_state(indoc! {"
 4380        one two
 4381        \tˇthree
 4382        four
 4383    "});
 4384    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4385    cx.assert_editor_state(indoc! {"
 4386        one two
 4387        ˇthree
 4388        four
 4389    "});
 4390}
 4391
 4392#[gpui::test]
 4393fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4394    init_test(cx, |settings| {
 4395        settings.languages.0.extend([
 4396            (
 4397                "TOML".into(),
 4398                LanguageSettingsContent {
 4399                    tab_size: NonZeroU32::new(2),
 4400                    ..Default::default()
 4401                },
 4402            ),
 4403            (
 4404                "Rust".into(),
 4405                LanguageSettingsContent {
 4406                    tab_size: NonZeroU32::new(4),
 4407                    ..Default::default()
 4408                },
 4409            ),
 4410        ]);
 4411    });
 4412
 4413    let toml_language = Arc::new(Language::new(
 4414        LanguageConfig {
 4415            name: "TOML".into(),
 4416            ..Default::default()
 4417        },
 4418        None,
 4419    ));
 4420    let rust_language = Arc::new(Language::new(
 4421        LanguageConfig {
 4422            name: "Rust".into(),
 4423            ..Default::default()
 4424        },
 4425        None,
 4426    ));
 4427
 4428    let toml_buffer =
 4429        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4430    let rust_buffer =
 4431        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4432    let multibuffer = cx.new(|cx| {
 4433        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4434        multibuffer.push_excerpts(
 4435            toml_buffer.clone(),
 4436            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4437            cx,
 4438        );
 4439        multibuffer.push_excerpts(
 4440            rust_buffer.clone(),
 4441            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4442            cx,
 4443        );
 4444        multibuffer
 4445    });
 4446
 4447    cx.add_window(|window, cx| {
 4448        let mut editor = build_editor(multibuffer, window, cx);
 4449
 4450        assert_eq!(
 4451            editor.text(cx),
 4452            indoc! {"
 4453                a = 1
 4454                b = 2
 4455
 4456                const c: usize = 3;
 4457            "}
 4458        );
 4459
 4460        select_ranges(
 4461            &mut editor,
 4462            indoc! {"
 4463                «aˇ» = 1
 4464                b = 2
 4465
 4466                «const c:ˇ» usize = 3;
 4467            "},
 4468            window,
 4469            cx,
 4470        );
 4471
 4472        editor.tab(&Tab, window, cx);
 4473        assert_text_with_selections(
 4474            &mut editor,
 4475            indoc! {"
 4476                  «aˇ» = 1
 4477                b = 2
 4478
 4479                    «const c:ˇ» usize = 3;
 4480            "},
 4481            cx,
 4482        );
 4483        editor.backtab(&Backtab, window, cx);
 4484        assert_text_with_selections(
 4485            &mut editor,
 4486            indoc! {"
 4487                «aˇ» = 1
 4488                b = 2
 4489
 4490                «const c:ˇ» usize = 3;
 4491            "},
 4492            cx,
 4493        );
 4494
 4495        editor
 4496    });
 4497}
 4498
 4499#[gpui::test]
 4500async fn test_backspace(cx: &mut TestAppContext) {
 4501    init_test(cx, |_| {});
 4502
 4503    let mut cx = EditorTestContext::new(cx).await;
 4504
 4505    // Basic backspace
 4506    cx.set_state(indoc! {"
 4507        onˇe two three
 4508        fou«rˇ» five six
 4509        seven «ˇeight nine
 4510        »ten
 4511    "});
 4512    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4513    cx.assert_editor_state(indoc! {"
 4514        oˇe two three
 4515        fouˇ five six
 4516        seven ˇten
 4517    "});
 4518
 4519    // Test backspace inside and around indents
 4520    cx.set_state(indoc! {"
 4521        zero
 4522            ˇone
 4523                ˇtwo
 4524            ˇ ˇ ˇ  three
 4525        ˇ  ˇ  four
 4526    "});
 4527    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4528    cx.assert_editor_state(indoc! {"
 4529        zero
 4530        ˇone
 4531            ˇtwo
 4532        ˇ  threeˇ  four
 4533    "});
 4534}
 4535
 4536#[gpui::test]
 4537async fn test_delete(cx: &mut TestAppContext) {
 4538    init_test(cx, |_| {});
 4539
 4540    let mut cx = EditorTestContext::new(cx).await;
 4541    cx.set_state(indoc! {"
 4542        onˇe two three
 4543        fou«rˇ» five six
 4544        seven «ˇeight nine
 4545        »ten
 4546    "});
 4547    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4548    cx.assert_editor_state(indoc! {"
 4549        onˇ two three
 4550        fouˇ five six
 4551        seven ˇten
 4552    "});
 4553}
 4554
 4555#[gpui::test]
 4556fn test_delete_line(cx: &mut TestAppContext) {
 4557    init_test(cx, |_| {});
 4558
 4559    let editor = cx.add_window(|window, cx| {
 4560        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4561        build_editor(buffer, window, cx)
 4562    });
 4563    _ = editor.update(cx, |editor, window, cx| {
 4564        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4565            s.select_display_ranges([
 4566                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4567                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4568                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4569            ])
 4570        });
 4571        editor.delete_line(&DeleteLine, window, cx);
 4572        assert_eq!(editor.display_text(cx), "ghi");
 4573        assert_eq!(
 4574            display_ranges(editor, cx),
 4575            vec![
 4576                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4577                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4578            ]
 4579        );
 4580    });
 4581
 4582    let editor = cx.add_window(|window, cx| {
 4583        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4584        build_editor(buffer, window, cx)
 4585    });
 4586    _ = editor.update(cx, |editor, window, cx| {
 4587        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4588            s.select_display_ranges([
 4589                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4590            ])
 4591        });
 4592        editor.delete_line(&DeleteLine, window, cx);
 4593        assert_eq!(editor.display_text(cx), "ghi\n");
 4594        assert_eq!(
 4595            display_ranges(editor, cx),
 4596            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4597        );
 4598    });
 4599
 4600    let editor = cx.add_window(|window, cx| {
 4601        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4602        build_editor(buffer, window, cx)
 4603    });
 4604    _ = editor.update(cx, |editor, window, cx| {
 4605        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4606            s.select_display_ranges([
 4607                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4608            ])
 4609        });
 4610        editor.delete_line(&DeleteLine, window, cx);
 4611        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4612        assert_eq!(
 4613            display_ranges(editor, cx),
 4614            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4615        );
 4616    });
 4617}
 4618
 4619#[gpui::test]
 4620fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4621    init_test(cx, |_| {});
 4622
 4623    cx.add_window(|window, cx| {
 4624        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4625        let mut editor = build_editor(buffer.clone(), window, cx);
 4626        let buffer = buffer.read(cx).as_singleton().unwrap();
 4627
 4628        assert_eq!(
 4629            editor
 4630                .selections
 4631                .ranges::<Point>(&editor.display_snapshot(cx)),
 4632            &[Point::new(0, 0)..Point::new(0, 0)]
 4633        );
 4634
 4635        // When on single line, replace newline at end by space
 4636        editor.join_lines(&JoinLines, window, cx);
 4637        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4638        assert_eq!(
 4639            editor
 4640                .selections
 4641                .ranges::<Point>(&editor.display_snapshot(cx)),
 4642            &[Point::new(0, 3)..Point::new(0, 3)]
 4643        );
 4644
 4645        // When multiple lines are selected, remove newlines that are spanned by the selection
 4646        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4647            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4648        });
 4649        editor.join_lines(&JoinLines, window, cx);
 4650        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4651        assert_eq!(
 4652            editor
 4653                .selections
 4654                .ranges::<Point>(&editor.display_snapshot(cx)),
 4655            &[Point::new(0, 11)..Point::new(0, 11)]
 4656        );
 4657
 4658        // Undo should be transactional
 4659        editor.undo(&Undo, window, cx);
 4660        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4661        assert_eq!(
 4662            editor
 4663                .selections
 4664                .ranges::<Point>(&editor.display_snapshot(cx)),
 4665            &[Point::new(0, 5)..Point::new(2, 2)]
 4666        );
 4667
 4668        // When joining an empty line don't insert a space
 4669        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4670            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4671        });
 4672        editor.join_lines(&JoinLines, window, cx);
 4673        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4674        assert_eq!(
 4675            editor
 4676                .selections
 4677                .ranges::<Point>(&editor.display_snapshot(cx)),
 4678            [Point::new(2, 3)..Point::new(2, 3)]
 4679        );
 4680
 4681        // We can remove trailing newlines
 4682        editor.join_lines(&JoinLines, window, cx);
 4683        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4684        assert_eq!(
 4685            editor
 4686                .selections
 4687                .ranges::<Point>(&editor.display_snapshot(cx)),
 4688            [Point::new(2, 3)..Point::new(2, 3)]
 4689        );
 4690
 4691        // We don't blow up on the last line
 4692        editor.join_lines(&JoinLines, window, cx);
 4693        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4694        assert_eq!(
 4695            editor
 4696                .selections
 4697                .ranges::<Point>(&editor.display_snapshot(cx)),
 4698            [Point::new(2, 3)..Point::new(2, 3)]
 4699        );
 4700
 4701        // reset to test indentation
 4702        editor.buffer.update(cx, |buffer, cx| {
 4703            buffer.edit(
 4704                [
 4705                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4706                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4707                ],
 4708                None,
 4709                cx,
 4710            )
 4711        });
 4712
 4713        // We remove any leading spaces
 4714        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4715        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4716            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4717        });
 4718        editor.join_lines(&JoinLines, window, cx);
 4719        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4720
 4721        // We don't insert a space for a line containing only spaces
 4722        editor.join_lines(&JoinLines, window, cx);
 4723        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4724
 4725        // We ignore any leading tabs
 4726        editor.join_lines(&JoinLines, window, cx);
 4727        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4728
 4729        editor
 4730    });
 4731}
 4732
 4733#[gpui::test]
 4734fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4735    init_test(cx, |_| {});
 4736
 4737    cx.add_window(|window, cx| {
 4738        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4739        let mut editor = build_editor(buffer.clone(), window, cx);
 4740        let buffer = buffer.read(cx).as_singleton().unwrap();
 4741
 4742        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4743            s.select_ranges([
 4744                Point::new(0, 2)..Point::new(1, 1),
 4745                Point::new(1, 2)..Point::new(1, 2),
 4746                Point::new(3, 1)..Point::new(3, 2),
 4747            ])
 4748        });
 4749
 4750        editor.join_lines(&JoinLines, window, cx);
 4751        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4752
 4753        assert_eq!(
 4754            editor
 4755                .selections
 4756                .ranges::<Point>(&editor.display_snapshot(cx)),
 4757            [
 4758                Point::new(0, 7)..Point::new(0, 7),
 4759                Point::new(1, 3)..Point::new(1, 3)
 4760            ]
 4761        );
 4762        editor
 4763    });
 4764}
 4765
 4766#[gpui::test]
 4767async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4768    init_test(cx, |_| {});
 4769
 4770    let mut cx = EditorTestContext::new(cx).await;
 4771
 4772    let diff_base = r#"
 4773        Line 0
 4774        Line 1
 4775        Line 2
 4776        Line 3
 4777        "#
 4778    .unindent();
 4779
 4780    cx.set_state(
 4781        &r#"
 4782        ˇLine 0
 4783        Line 1
 4784        Line 2
 4785        Line 3
 4786        "#
 4787        .unindent(),
 4788    );
 4789
 4790    cx.set_head_text(&diff_base);
 4791    executor.run_until_parked();
 4792
 4793    // Join lines
 4794    cx.update_editor(|editor, window, cx| {
 4795        editor.join_lines(&JoinLines, window, cx);
 4796    });
 4797    executor.run_until_parked();
 4798
 4799    cx.assert_editor_state(
 4800        &r#"
 4801        Line 0ˇ Line 1
 4802        Line 2
 4803        Line 3
 4804        "#
 4805        .unindent(),
 4806    );
 4807    // Join again
 4808    cx.update_editor(|editor, window, cx| {
 4809        editor.join_lines(&JoinLines, window, cx);
 4810    });
 4811    executor.run_until_parked();
 4812
 4813    cx.assert_editor_state(
 4814        &r#"
 4815        Line 0 Line 1ˇ Line 2
 4816        Line 3
 4817        "#
 4818        .unindent(),
 4819    );
 4820}
 4821
 4822#[gpui::test]
 4823async fn test_custom_newlines_cause_no_false_positive_diffs(
 4824    executor: BackgroundExecutor,
 4825    cx: &mut TestAppContext,
 4826) {
 4827    init_test(cx, |_| {});
 4828    let mut cx = EditorTestContext::new(cx).await;
 4829    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4830    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4831    executor.run_until_parked();
 4832
 4833    cx.update_editor(|editor, window, cx| {
 4834        let snapshot = editor.snapshot(window, cx);
 4835        assert_eq!(
 4836            snapshot
 4837                .buffer_snapshot()
 4838                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 4839                .collect::<Vec<_>>(),
 4840            Vec::new(),
 4841            "Should not have any diffs for files with custom newlines"
 4842        );
 4843    });
 4844}
 4845
 4846#[gpui::test]
 4847async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4848    init_test(cx, |_| {});
 4849
 4850    let mut cx = EditorTestContext::new(cx).await;
 4851
 4852    // Test sort_lines_case_insensitive()
 4853    cx.set_state(indoc! {"
 4854        «z
 4855        y
 4856        x
 4857        Z
 4858        Y
 4859        Xˇ»
 4860    "});
 4861    cx.update_editor(|e, window, cx| {
 4862        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4863    });
 4864    cx.assert_editor_state(indoc! {"
 4865        «x
 4866        X
 4867        y
 4868        Y
 4869        z
 4870        Zˇ»
 4871    "});
 4872
 4873    // Test sort_lines_by_length()
 4874    //
 4875    // Demonstrates:
 4876    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4877    // - sort is stable
 4878    cx.set_state(indoc! {"
 4879        «123
 4880        æ
 4881        12
 4882 4883        1
 4884        æˇ»
 4885    "});
 4886    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4887    cx.assert_editor_state(indoc! {"
 4888        «æ
 4889 4890        1
 4891        æ
 4892        12
 4893        123ˇ»
 4894    "});
 4895
 4896    // Test reverse_lines()
 4897    cx.set_state(indoc! {"
 4898        «5
 4899        4
 4900        3
 4901        2
 4902        1ˇ»
 4903    "});
 4904    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4905    cx.assert_editor_state(indoc! {"
 4906        «1
 4907        2
 4908        3
 4909        4
 4910        5ˇ»
 4911    "});
 4912
 4913    // Skip testing shuffle_line()
 4914
 4915    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4916    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4917
 4918    // Don't manipulate when cursor is on single line, but expand the selection
 4919    cx.set_state(indoc! {"
 4920        ddˇdd
 4921        ccc
 4922        bb
 4923        a
 4924    "});
 4925    cx.update_editor(|e, window, cx| {
 4926        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4927    });
 4928    cx.assert_editor_state(indoc! {"
 4929        «ddddˇ»
 4930        ccc
 4931        bb
 4932        a
 4933    "});
 4934
 4935    // Basic manipulate case
 4936    // Start selection moves to column 0
 4937    // End of selection shrinks to fit shorter line
 4938    cx.set_state(indoc! {"
 4939        dd«d
 4940        ccc
 4941        bb
 4942        aaaaaˇ»
 4943    "});
 4944    cx.update_editor(|e, window, cx| {
 4945        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4946    });
 4947    cx.assert_editor_state(indoc! {"
 4948        «aaaaa
 4949        bb
 4950        ccc
 4951        dddˇ»
 4952    "});
 4953
 4954    // Manipulate case with newlines
 4955    cx.set_state(indoc! {"
 4956        dd«d
 4957        ccc
 4958
 4959        bb
 4960        aaaaa
 4961
 4962        ˇ»
 4963    "});
 4964    cx.update_editor(|e, window, cx| {
 4965        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4966    });
 4967    cx.assert_editor_state(indoc! {"
 4968        «
 4969
 4970        aaaaa
 4971        bb
 4972        ccc
 4973        dddˇ»
 4974
 4975    "});
 4976
 4977    // Adding new line
 4978    cx.set_state(indoc! {"
 4979        aa«a
 4980        bbˇ»b
 4981    "});
 4982    cx.update_editor(|e, window, cx| {
 4983        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4984    });
 4985    cx.assert_editor_state(indoc! {"
 4986        «aaa
 4987        bbb
 4988        added_lineˇ»
 4989    "});
 4990
 4991    // Removing line
 4992    cx.set_state(indoc! {"
 4993        aa«a
 4994        bbbˇ»
 4995    "});
 4996    cx.update_editor(|e, window, cx| {
 4997        e.manipulate_immutable_lines(window, cx, |lines| {
 4998            lines.pop();
 4999        })
 5000    });
 5001    cx.assert_editor_state(indoc! {"
 5002        «aaaˇ»
 5003    "});
 5004
 5005    // Removing all lines
 5006    cx.set_state(indoc! {"
 5007        aa«a
 5008        bbbˇ»
 5009    "});
 5010    cx.update_editor(|e, window, cx| {
 5011        e.manipulate_immutable_lines(window, cx, |lines| {
 5012            lines.drain(..);
 5013        })
 5014    });
 5015    cx.assert_editor_state(indoc! {"
 5016        ˇ
 5017    "});
 5018}
 5019
 5020#[gpui::test]
 5021async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 5022    init_test(cx, |_| {});
 5023
 5024    let mut cx = EditorTestContext::new(cx).await;
 5025
 5026    // Consider continuous selection as single selection
 5027    cx.set_state(indoc! {"
 5028        Aaa«aa
 5029        cˇ»c«c
 5030        bb
 5031        aaaˇ»aa
 5032    "});
 5033    cx.update_editor(|e, window, cx| {
 5034        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5035    });
 5036    cx.assert_editor_state(indoc! {"
 5037        «Aaaaa
 5038        ccc
 5039        bb
 5040        aaaaaˇ»
 5041    "});
 5042
 5043    cx.set_state(indoc! {"
 5044        Aaa«aa
 5045        cˇ»c«c
 5046        bb
 5047        aaaˇ»aa
 5048    "});
 5049    cx.update_editor(|e, window, cx| {
 5050        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5051    });
 5052    cx.assert_editor_state(indoc! {"
 5053        «Aaaaa
 5054        ccc
 5055        bbˇ»
 5056    "});
 5057
 5058    // Consider non continuous selection as distinct dedup operations
 5059    cx.set_state(indoc! {"
 5060        «aaaaa
 5061        bb
 5062        aaaaa
 5063        aaaaaˇ»
 5064
 5065        aaa«aaˇ»
 5066    "});
 5067    cx.update_editor(|e, window, cx| {
 5068        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5069    });
 5070    cx.assert_editor_state(indoc! {"
 5071        «aaaaa
 5072        bbˇ»
 5073
 5074        «aaaaaˇ»
 5075    "});
 5076}
 5077
 5078#[gpui::test]
 5079async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 5080    init_test(cx, |_| {});
 5081
 5082    let mut cx = EditorTestContext::new(cx).await;
 5083
 5084    cx.set_state(indoc! {"
 5085        «Aaa
 5086        aAa
 5087        Aaaˇ»
 5088    "});
 5089    cx.update_editor(|e, window, cx| {
 5090        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5091    });
 5092    cx.assert_editor_state(indoc! {"
 5093        «Aaa
 5094        aAaˇ»
 5095    "});
 5096
 5097    cx.set_state(indoc! {"
 5098        «Aaa
 5099        aAa
 5100        aaAˇ»
 5101    "});
 5102    cx.update_editor(|e, window, cx| {
 5103        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5104    });
 5105    cx.assert_editor_state(indoc! {"
 5106        «Aaaˇ»
 5107    "});
 5108}
 5109
 5110#[gpui::test]
 5111async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5112    init_test(cx, |_| {});
 5113
 5114    let mut cx = EditorTestContext::new(cx).await;
 5115
 5116    let js_language = Arc::new(Language::new(
 5117        LanguageConfig {
 5118            name: "JavaScript".into(),
 5119            wrap_characters: Some(language::WrapCharactersConfig {
 5120                start_prefix: "<".into(),
 5121                start_suffix: ">".into(),
 5122                end_prefix: "</".into(),
 5123                end_suffix: ">".into(),
 5124            }),
 5125            ..LanguageConfig::default()
 5126        },
 5127        None,
 5128    ));
 5129
 5130    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5131
 5132    cx.set_state(indoc! {"
 5133        «testˇ»
 5134    "});
 5135    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5136    cx.assert_editor_state(indoc! {"
 5137        <«ˇ»>test</«ˇ»>
 5138    "});
 5139
 5140    cx.set_state(indoc! {"
 5141        «test
 5142         testˇ»
 5143    "});
 5144    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5145    cx.assert_editor_state(indoc! {"
 5146        <«ˇ»>test
 5147         test</«ˇ»>
 5148    "});
 5149
 5150    cx.set_state(indoc! {"
 5151        teˇst
 5152    "});
 5153    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5154    cx.assert_editor_state(indoc! {"
 5155        te<«ˇ»></«ˇ»>st
 5156    "});
 5157}
 5158
 5159#[gpui::test]
 5160async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5161    init_test(cx, |_| {});
 5162
 5163    let mut cx = EditorTestContext::new(cx).await;
 5164
 5165    let js_language = Arc::new(Language::new(
 5166        LanguageConfig {
 5167            name: "JavaScript".into(),
 5168            wrap_characters: Some(language::WrapCharactersConfig {
 5169                start_prefix: "<".into(),
 5170                start_suffix: ">".into(),
 5171                end_prefix: "</".into(),
 5172                end_suffix: ">".into(),
 5173            }),
 5174            ..LanguageConfig::default()
 5175        },
 5176        None,
 5177    ));
 5178
 5179    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5180
 5181    cx.set_state(indoc! {"
 5182        «testˇ»
 5183        «testˇ» «testˇ»
 5184        «testˇ»
 5185    "});
 5186    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5187    cx.assert_editor_state(indoc! {"
 5188        <«ˇ»>test</«ˇ»>
 5189        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5190        <«ˇ»>test</«ˇ»>
 5191    "});
 5192
 5193    cx.set_state(indoc! {"
 5194        «test
 5195         testˇ»
 5196        «test
 5197         testˇ»
 5198    "});
 5199    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5200    cx.assert_editor_state(indoc! {"
 5201        <«ˇ»>test
 5202         test</«ˇ»>
 5203        <«ˇ»>test
 5204         test</«ˇ»>
 5205    "});
 5206}
 5207
 5208#[gpui::test]
 5209async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5210    init_test(cx, |_| {});
 5211
 5212    let mut cx = EditorTestContext::new(cx).await;
 5213
 5214    let plaintext_language = Arc::new(Language::new(
 5215        LanguageConfig {
 5216            name: "Plain Text".into(),
 5217            ..LanguageConfig::default()
 5218        },
 5219        None,
 5220    ));
 5221
 5222    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5223
 5224    cx.set_state(indoc! {"
 5225        «testˇ»
 5226    "});
 5227    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5228    cx.assert_editor_state(indoc! {"
 5229      «testˇ»
 5230    "});
 5231}
 5232
 5233#[gpui::test]
 5234async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5235    init_test(cx, |_| {});
 5236
 5237    let mut cx = EditorTestContext::new(cx).await;
 5238
 5239    // Manipulate with multiple selections on a single line
 5240    cx.set_state(indoc! {"
 5241        dd«dd
 5242        cˇ»c«c
 5243        bb
 5244        aaaˇ»aa
 5245    "});
 5246    cx.update_editor(|e, window, cx| {
 5247        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5248    });
 5249    cx.assert_editor_state(indoc! {"
 5250        «aaaaa
 5251        bb
 5252        ccc
 5253        ddddˇ»
 5254    "});
 5255
 5256    // Manipulate with multiple disjoin selections
 5257    cx.set_state(indoc! {"
 5258 5259        4
 5260        3
 5261        2
 5262        1ˇ»
 5263
 5264        dd«dd
 5265        ccc
 5266        bb
 5267        aaaˇ»aa
 5268    "});
 5269    cx.update_editor(|e, window, cx| {
 5270        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5271    });
 5272    cx.assert_editor_state(indoc! {"
 5273        «1
 5274        2
 5275        3
 5276        4
 5277        5ˇ»
 5278
 5279        «aaaaa
 5280        bb
 5281        ccc
 5282        ddddˇ»
 5283    "});
 5284
 5285    // Adding lines on each selection
 5286    cx.set_state(indoc! {"
 5287 5288        1ˇ»
 5289
 5290        bb«bb
 5291        aaaˇ»aa
 5292    "});
 5293    cx.update_editor(|e, window, cx| {
 5294        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5295    });
 5296    cx.assert_editor_state(indoc! {"
 5297        «2
 5298        1
 5299        added lineˇ»
 5300
 5301        «bbbb
 5302        aaaaa
 5303        added lineˇ»
 5304    "});
 5305
 5306    // Removing lines on each selection
 5307    cx.set_state(indoc! {"
 5308 5309        1ˇ»
 5310
 5311        bb«bb
 5312        aaaˇ»aa
 5313    "});
 5314    cx.update_editor(|e, window, cx| {
 5315        e.manipulate_immutable_lines(window, cx, |lines| {
 5316            lines.pop();
 5317        })
 5318    });
 5319    cx.assert_editor_state(indoc! {"
 5320        «2ˇ»
 5321
 5322        «bbbbˇ»
 5323    "});
 5324}
 5325
 5326#[gpui::test]
 5327async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5328    init_test(cx, |settings| {
 5329        settings.defaults.tab_size = NonZeroU32::new(3)
 5330    });
 5331
 5332    let mut cx = EditorTestContext::new(cx).await;
 5333
 5334    // MULTI SELECTION
 5335    // Ln.1 "«" tests empty lines
 5336    // Ln.9 tests just leading whitespace
 5337    cx.set_state(indoc! {"
 5338        «
 5339        abc                 // No indentationˇ»
 5340        «\tabc              // 1 tabˇ»
 5341        \t\tabc «      ˇ»   // 2 tabs
 5342        \t ab«c             // Tab followed by space
 5343         \tabc              // Space followed by tab (3 spaces should be the result)
 5344        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5345           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5346        \t
 5347        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5348    "});
 5349    cx.update_editor(|e, window, cx| {
 5350        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5351    });
 5352    cx.assert_editor_state(
 5353        indoc! {"
 5354            «
 5355            abc                 // No indentation
 5356               abc              // 1 tab
 5357                  abc          // 2 tabs
 5358                abc             // Tab followed by space
 5359               abc              // Space followed by tab (3 spaces should be the result)
 5360                           abc   // Mixed indentation (tab conversion depends on the column)
 5361               abc         // Already space indented
 5362               ·
 5363               abc\tdef          // Only the leading tab is manipulatedˇ»
 5364        "}
 5365        .replace("·", "")
 5366        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5367    );
 5368
 5369    // Test on just a few lines, the others should remain unchanged
 5370    // Only lines (3, 5, 10, 11) should change
 5371    cx.set_state(
 5372        indoc! {"
 5373            ·
 5374            abc                 // No indentation
 5375            \tabcˇ               // 1 tab
 5376            \t\tabc             // 2 tabs
 5377            \t abcˇ              // Tab followed by space
 5378             \tabc              // Space followed by tab (3 spaces should be the result)
 5379            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5380               abc              // Already space indented
 5381            «\t
 5382            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5383        "}
 5384        .replace("·", "")
 5385        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5386    );
 5387    cx.update_editor(|e, window, cx| {
 5388        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5389    });
 5390    cx.assert_editor_state(
 5391        indoc! {"
 5392            ·
 5393            abc                 // No indentation
 5394            «   abc               // 1 tabˇ»
 5395            \t\tabc             // 2 tabs
 5396            «    abc              // Tab followed by spaceˇ»
 5397             \tabc              // Space followed by tab (3 spaces should be the result)
 5398            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5399               abc              // Already space indented
 5400            «   ·
 5401               abc\tdef          // Only the leading tab is manipulatedˇ»
 5402        "}
 5403        .replace("·", "")
 5404        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5405    );
 5406
 5407    // SINGLE SELECTION
 5408    // Ln.1 "«" tests empty lines
 5409    // Ln.9 tests just leading whitespace
 5410    cx.set_state(indoc! {"
 5411        «
 5412        abc                 // No indentation
 5413        \tabc               // 1 tab
 5414        \t\tabc             // 2 tabs
 5415        \t abc              // Tab followed by space
 5416         \tabc              // Space followed by tab (3 spaces should be the result)
 5417        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5418           abc              // Already space indented
 5419        \t
 5420        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5421    "});
 5422    cx.update_editor(|e, window, cx| {
 5423        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5424    });
 5425    cx.assert_editor_state(
 5426        indoc! {"
 5427            «
 5428            abc                 // No indentation
 5429               abc               // 1 tab
 5430                  abc             // 2 tabs
 5431                abc              // Tab followed by space
 5432               abc              // Space followed by tab (3 spaces should be the result)
 5433                           abc   // Mixed indentation (tab conversion depends on the column)
 5434               abc              // Already space indented
 5435               ·
 5436               abc\tdef          // Only the leading tab is manipulatedˇ»
 5437        "}
 5438        .replace("·", "")
 5439        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5440    );
 5441}
 5442
 5443#[gpui::test]
 5444async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5445    init_test(cx, |settings| {
 5446        settings.defaults.tab_size = NonZeroU32::new(3)
 5447    });
 5448
 5449    let mut cx = EditorTestContext::new(cx).await;
 5450
 5451    // MULTI SELECTION
 5452    // Ln.1 "«" tests empty lines
 5453    // Ln.11 tests just leading whitespace
 5454    cx.set_state(indoc! {"
 5455        «
 5456        abˇ»ˇc                 // No indentation
 5457         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5458          abc  «             // 2 spaces (< 3 so dont convert)
 5459           abc              // 3 spaces (convert)
 5460             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5461        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5462        «\t abc              // Tab followed by space
 5463         \tabc              // Space followed by tab (should be consumed due to tab)
 5464        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5465           \tˇ»  «\t
 5466           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5467    "});
 5468    cx.update_editor(|e, window, cx| {
 5469        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5470    });
 5471    cx.assert_editor_state(indoc! {"
 5472        «
 5473        abc                 // No indentation
 5474         abc                // 1 space (< 3 so dont convert)
 5475          abc               // 2 spaces (< 3 so dont convert)
 5476        \tabc              // 3 spaces (convert)
 5477        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5478        \t\t\tabc           // Already tab indented
 5479        \t abc              // Tab followed by space
 5480        \tabc              // Space followed by tab (should be consumed due to tab)
 5481        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5482        \t\t\t
 5483        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5484    "});
 5485
 5486    // Test on just a few lines, the other should remain unchanged
 5487    // Only lines (4, 8, 11, 12) should change
 5488    cx.set_state(
 5489        indoc! {"
 5490            ·
 5491            abc                 // No indentation
 5492             abc                // 1 space (< 3 so dont convert)
 5493              abc               // 2 spaces (< 3 so dont convert)
 5494            «   abc              // 3 spaces (convert)ˇ»
 5495                 abc            // 5 spaces (1 tab + 2 spaces)
 5496            \t\t\tabc           // Already tab indented
 5497            \t abc              // Tab followed by space
 5498             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5499               \t\t  \tabc      // Mixed indentation
 5500            \t \t  \t   \tabc   // Mixed indentation
 5501               \t  \tˇ
 5502            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5503        "}
 5504        .replace("·", "")
 5505        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5506    );
 5507    cx.update_editor(|e, window, cx| {
 5508        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5509    });
 5510    cx.assert_editor_state(
 5511        indoc! {"
 5512            ·
 5513            abc                 // No indentation
 5514             abc                // 1 space (< 3 so dont convert)
 5515              abc               // 2 spaces (< 3 so dont convert)
 5516            «\tabc              // 3 spaces (convert)ˇ»
 5517                 abc            // 5 spaces (1 tab + 2 spaces)
 5518            \t\t\tabc           // Already tab indented
 5519            \t abc              // Tab followed by space
 5520            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5521               \t\t  \tabc      // Mixed indentation
 5522            \t \t  \t   \tabc   // Mixed indentation
 5523            «\t\t\t
 5524            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5525        "}
 5526        .replace("·", "")
 5527        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5528    );
 5529
 5530    // SINGLE SELECTION
 5531    // Ln.1 "«" tests empty lines
 5532    // Ln.11 tests just leading whitespace
 5533    cx.set_state(indoc! {"
 5534        «
 5535        abc                 // No indentation
 5536         abc                // 1 space (< 3 so dont convert)
 5537          abc               // 2 spaces (< 3 so dont convert)
 5538           abc              // 3 spaces (convert)
 5539             abc            // 5 spaces (1 tab + 2 spaces)
 5540        \t\t\tabc           // Already tab indented
 5541        \t abc              // Tab followed by space
 5542         \tabc              // Space followed by tab (should be consumed due to tab)
 5543        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5544           \t  \t
 5545           abc   \t         // Only the leading spaces should be convertedˇ»
 5546    "});
 5547    cx.update_editor(|e, window, cx| {
 5548        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5549    });
 5550    cx.assert_editor_state(indoc! {"
 5551        «
 5552        abc                 // No indentation
 5553         abc                // 1 space (< 3 so dont convert)
 5554          abc               // 2 spaces (< 3 so dont convert)
 5555        \tabc              // 3 spaces (convert)
 5556        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5557        \t\t\tabc           // Already tab indented
 5558        \t abc              // Tab followed by space
 5559        \tabc              // Space followed by tab (should be consumed due to tab)
 5560        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5561        \t\t\t
 5562        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5563    "});
 5564}
 5565
 5566#[gpui::test]
 5567async fn test_toggle_case(cx: &mut TestAppContext) {
 5568    init_test(cx, |_| {});
 5569
 5570    let mut cx = EditorTestContext::new(cx).await;
 5571
 5572    // If all lower case -> upper case
 5573    cx.set_state(indoc! {"
 5574        «hello worldˇ»
 5575    "});
 5576    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5577    cx.assert_editor_state(indoc! {"
 5578        «HELLO WORLDˇ»
 5579    "});
 5580
 5581    // If all upper case -> lower case
 5582    cx.set_state(indoc! {"
 5583        «HELLO WORLDˇ»
 5584    "});
 5585    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5586    cx.assert_editor_state(indoc! {"
 5587        «hello worldˇ»
 5588    "});
 5589
 5590    // If any upper case characters are identified -> lower case
 5591    // This matches JetBrains IDEs
 5592    cx.set_state(indoc! {"
 5593        «hEllo worldˇ»
 5594    "});
 5595    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5596    cx.assert_editor_state(indoc! {"
 5597        «hello worldˇ»
 5598    "});
 5599}
 5600
 5601#[gpui::test]
 5602async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5603    init_test(cx, |_| {});
 5604
 5605    let mut cx = EditorTestContext::new(cx).await;
 5606
 5607    cx.set_state(indoc! {"
 5608        «implement-windows-supportˇ»
 5609    "});
 5610    cx.update_editor(|e, window, cx| {
 5611        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5612    });
 5613    cx.assert_editor_state(indoc! {"
 5614        «Implement windows supportˇ»
 5615    "});
 5616}
 5617
 5618#[gpui::test]
 5619async fn test_manipulate_text(cx: &mut TestAppContext) {
 5620    init_test(cx, |_| {});
 5621
 5622    let mut cx = EditorTestContext::new(cx).await;
 5623
 5624    // Test convert_to_upper_case()
 5625    cx.set_state(indoc! {"
 5626        «hello worldˇ»
 5627    "});
 5628    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5629    cx.assert_editor_state(indoc! {"
 5630        «HELLO WORLDˇ»
 5631    "});
 5632
 5633    // Test convert_to_lower_case()
 5634    cx.set_state(indoc! {"
 5635        «HELLO WORLDˇ»
 5636    "});
 5637    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5638    cx.assert_editor_state(indoc! {"
 5639        «hello worldˇ»
 5640    "});
 5641
 5642    // Test multiple line, single selection case
 5643    cx.set_state(indoc! {"
 5644        «The quick brown
 5645        fox jumps over
 5646        the lazy dogˇ»
 5647    "});
 5648    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5649    cx.assert_editor_state(indoc! {"
 5650        «The Quick Brown
 5651        Fox Jumps Over
 5652        The Lazy Dogˇ»
 5653    "});
 5654
 5655    // Test multiple line, single selection case
 5656    cx.set_state(indoc! {"
 5657        «The quick brown
 5658        fox jumps over
 5659        the lazy dogˇ»
 5660    "});
 5661    cx.update_editor(|e, window, cx| {
 5662        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5663    });
 5664    cx.assert_editor_state(indoc! {"
 5665        «TheQuickBrown
 5666        FoxJumpsOver
 5667        TheLazyDogˇ»
 5668    "});
 5669
 5670    // From here on out, test more complex cases of manipulate_text()
 5671
 5672    // Test no selection case - should affect words cursors are in
 5673    // Cursor at beginning, middle, and end of word
 5674    cx.set_state(indoc! {"
 5675        ˇhello big beauˇtiful worldˇ
 5676    "});
 5677    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5678    cx.assert_editor_state(indoc! {"
 5679        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5680    "});
 5681
 5682    // Test multiple selections on a single line and across multiple lines
 5683    cx.set_state(indoc! {"
 5684        «Theˇ» quick «brown
 5685        foxˇ» jumps «overˇ»
 5686        the «lazyˇ» dog
 5687    "});
 5688    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5689    cx.assert_editor_state(indoc! {"
 5690        «THEˇ» quick «BROWN
 5691        FOXˇ» jumps «OVERˇ»
 5692        the «LAZYˇ» dog
 5693    "});
 5694
 5695    // Test case where text length grows
 5696    cx.set_state(indoc! {"
 5697        «tschüߡ»
 5698    "});
 5699    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5700    cx.assert_editor_state(indoc! {"
 5701        «TSCHÜSSˇ»
 5702    "});
 5703
 5704    // Test to make sure we don't crash when text shrinks
 5705    cx.set_state(indoc! {"
 5706        aaa_bbbˇ
 5707    "});
 5708    cx.update_editor(|e, window, cx| {
 5709        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5710    });
 5711    cx.assert_editor_state(indoc! {"
 5712        «aaaBbbˇ»
 5713    "});
 5714
 5715    // Test to make sure we all aware of the fact that each word can grow and shrink
 5716    // Final selections should be aware of this fact
 5717    cx.set_state(indoc! {"
 5718        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5719    "});
 5720    cx.update_editor(|e, window, cx| {
 5721        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5722    });
 5723    cx.assert_editor_state(indoc! {"
 5724        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5725    "});
 5726
 5727    cx.set_state(indoc! {"
 5728        «hElLo, WoRld!ˇ»
 5729    "});
 5730    cx.update_editor(|e, window, cx| {
 5731        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5732    });
 5733    cx.assert_editor_state(indoc! {"
 5734        «HeLlO, wOrLD!ˇ»
 5735    "});
 5736
 5737    // Test selections with `line_mode() = true`.
 5738    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5739    cx.set_state(indoc! {"
 5740        «The quick brown
 5741        fox jumps over
 5742        tˇ»he lazy dog
 5743    "});
 5744    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5745    cx.assert_editor_state(indoc! {"
 5746        «THE QUICK BROWN
 5747        FOX JUMPS OVER
 5748        THE LAZY DOGˇ»
 5749    "});
 5750}
 5751
 5752#[gpui::test]
 5753fn test_duplicate_line(cx: &mut TestAppContext) {
 5754    init_test(cx, |_| {});
 5755
 5756    let editor = cx.add_window(|window, cx| {
 5757        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5758        build_editor(buffer, window, cx)
 5759    });
 5760    _ = editor.update(cx, |editor, window, cx| {
 5761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5762            s.select_display_ranges([
 5763                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5764                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5765                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5766                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5767            ])
 5768        });
 5769        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5770        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5771        assert_eq!(
 5772            display_ranges(editor, cx),
 5773            vec![
 5774                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5775                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5776                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5777                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5778            ]
 5779        );
 5780    });
 5781
 5782    let editor = cx.add_window(|window, cx| {
 5783        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5784        build_editor(buffer, window, cx)
 5785    });
 5786    _ = editor.update(cx, |editor, window, cx| {
 5787        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5788            s.select_display_ranges([
 5789                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5790                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5791            ])
 5792        });
 5793        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5794        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5795        assert_eq!(
 5796            display_ranges(editor, cx),
 5797            vec![
 5798                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5799                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5800            ]
 5801        );
 5802    });
 5803
 5804    // With `duplicate_line_up` the selections move to the duplicated lines,
 5805    // which are inserted above the original lines
 5806    let editor = cx.add_window(|window, cx| {
 5807        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5808        build_editor(buffer, window, cx)
 5809    });
 5810    _ = editor.update(cx, |editor, window, cx| {
 5811        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5812            s.select_display_ranges([
 5813                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5814                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5815                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5816                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5817            ])
 5818        });
 5819        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5820        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5821        assert_eq!(
 5822            display_ranges(editor, cx),
 5823            vec![
 5824                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5825                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5826                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5827                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5828            ]
 5829        );
 5830    });
 5831
 5832    let editor = cx.add_window(|window, cx| {
 5833        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5834        build_editor(buffer, window, cx)
 5835    });
 5836    _ = editor.update(cx, |editor, window, cx| {
 5837        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5838            s.select_display_ranges([
 5839                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5840                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5841            ])
 5842        });
 5843        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5844        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5845        assert_eq!(
 5846            display_ranges(editor, cx),
 5847            vec![
 5848                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5849                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5850            ]
 5851        );
 5852    });
 5853
 5854    let editor = cx.add_window(|window, cx| {
 5855        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5856        build_editor(buffer, window, cx)
 5857    });
 5858    _ = editor.update(cx, |editor, window, cx| {
 5859        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5860            s.select_display_ranges([
 5861                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5862                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5863            ])
 5864        });
 5865        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5866        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5867        assert_eq!(
 5868            display_ranges(editor, cx),
 5869            vec![
 5870                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5871                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5872            ]
 5873        );
 5874    });
 5875}
 5876
 5877#[gpui::test]
 5878async fn test_rotate_selections(cx: &mut TestAppContext) {
 5879    init_test(cx, |_| {});
 5880
 5881    let mut cx = EditorTestContext::new(cx).await;
 5882
 5883    // Rotate text selections (horizontal)
 5884    cx.set_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5885    cx.update_editor(|e, window, cx| {
 5886        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5887    });
 5888    cx.assert_editor_state("x=«3ˇ», y=«1ˇ», z=«2ˇ»");
 5889    cx.update_editor(|e, window, cx| {
 5890        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5891    });
 5892    cx.assert_editor_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5893
 5894    // Rotate text selections (vertical)
 5895    cx.set_state(indoc! {"
 5896        x=«1ˇ»
 5897        y=«2ˇ»
 5898        z=«3ˇ»
 5899    "});
 5900    cx.update_editor(|e, window, cx| {
 5901        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5902    });
 5903    cx.assert_editor_state(indoc! {"
 5904        x=«3ˇ»
 5905        y=«1ˇ»
 5906        z=«2ˇ»
 5907    "});
 5908    cx.update_editor(|e, window, cx| {
 5909        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5910    });
 5911    cx.assert_editor_state(indoc! {"
 5912        x=«1ˇ»
 5913        y=«2ˇ»
 5914        z=«3ˇ»
 5915    "});
 5916
 5917    // Rotate text selections (vertical, different lengths)
 5918    cx.set_state(indoc! {"
 5919        x=\"«ˇ»\"
 5920        y=\"«aˇ»\"
 5921        z=\"«aaˇ»\"
 5922    "});
 5923    cx.update_editor(|e, window, cx| {
 5924        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5925    });
 5926    cx.assert_editor_state(indoc! {"
 5927        x=\"«aaˇ»\"
 5928        y=\"«ˇ»\"
 5929        z=\"«aˇ»\"
 5930    "});
 5931    cx.update_editor(|e, window, cx| {
 5932        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5933    });
 5934    cx.assert_editor_state(indoc! {"
 5935        x=\"«ˇ»\"
 5936        y=\"«aˇ»\"
 5937        z=\"«aaˇ»\"
 5938    "});
 5939
 5940    // Rotate whole lines (cursor positions preserved)
 5941    cx.set_state(indoc! {"
 5942        ˇline123
 5943        liˇne23
 5944        line3ˇ
 5945    "});
 5946    cx.update_editor(|e, window, cx| {
 5947        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5948    });
 5949    cx.assert_editor_state(indoc! {"
 5950        line3ˇ
 5951        ˇline123
 5952        liˇne23
 5953    "});
 5954    cx.update_editor(|e, window, cx| {
 5955        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5956    });
 5957    cx.assert_editor_state(indoc! {"
 5958        ˇline123
 5959        liˇne23
 5960        line3ˇ
 5961    "});
 5962
 5963    // Rotate whole lines, multiple cursors per line (positions preserved)
 5964    cx.set_state(indoc! {"
 5965        ˇliˇne123
 5966        ˇline23
 5967        ˇline3
 5968    "});
 5969    cx.update_editor(|e, window, cx| {
 5970        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5971    });
 5972    cx.assert_editor_state(indoc! {"
 5973        ˇline3
 5974        ˇliˇne123
 5975        ˇline23
 5976    "});
 5977    cx.update_editor(|e, window, cx| {
 5978        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5979    });
 5980    cx.assert_editor_state(indoc! {"
 5981        ˇliˇne123
 5982        ˇline23
 5983        ˇline3
 5984    "});
 5985}
 5986
 5987#[gpui::test]
 5988fn test_move_line_up_down(cx: &mut TestAppContext) {
 5989    init_test(cx, |_| {});
 5990
 5991    let editor = cx.add_window(|window, cx| {
 5992        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5993        build_editor(buffer, window, cx)
 5994    });
 5995    _ = editor.update(cx, |editor, window, cx| {
 5996        editor.fold_creases(
 5997            vec![
 5998                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5999                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6000                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6001            ],
 6002            true,
 6003            window,
 6004            cx,
 6005        );
 6006        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6007            s.select_display_ranges([
 6008                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 6009                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6010                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6011                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 6012            ])
 6013        });
 6014        assert_eq!(
 6015            editor.display_text(cx),
 6016            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 6017        );
 6018
 6019        editor.move_line_up(&MoveLineUp, window, cx);
 6020        assert_eq!(
 6021            editor.display_text(cx),
 6022            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 6023        );
 6024        assert_eq!(
 6025            display_ranges(editor, cx),
 6026            vec![
 6027                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 6028                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6029                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6030                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6031            ]
 6032        );
 6033    });
 6034
 6035    _ = editor.update(cx, |editor, window, cx| {
 6036        editor.move_line_down(&MoveLineDown, window, cx);
 6037        assert_eq!(
 6038            editor.display_text(cx),
 6039            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 6040        );
 6041        assert_eq!(
 6042            display_ranges(editor, cx),
 6043            vec![
 6044                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6045                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6046                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6047                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6048            ]
 6049        );
 6050    });
 6051
 6052    _ = editor.update(cx, |editor, window, cx| {
 6053        editor.move_line_down(&MoveLineDown, window, cx);
 6054        assert_eq!(
 6055            editor.display_text(cx),
 6056            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 6057        );
 6058        assert_eq!(
 6059            display_ranges(editor, cx),
 6060            vec![
 6061                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6062                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6063                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6064                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6065            ]
 6066        );
 6067    });
 6068
 6069    _ = editor.update(cx, |editor, window, cx| {
 6070        editor.move_line_up(&MoveLineUp, window, cx);
 6071        assert_eq!(
 6072            editor.display_text(cx),
 6073            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 6074        );
 6075        assert_eq!(
 6076            display_ranges(editor, cx),
 6077            vec![
 6078                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6079                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6080                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6081                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6082            ]
 6083        );
 6084    });
 6085}
 6086
 6087#[gpui::test]
 6088fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 6089    init_test(cx, |_| {});
 6090    let editor = cx.add_window(|window, cx| {
 6091        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 6092        build_editor(buffer, window, cx)
 6093    });
 6094    _ = editor.update(cx, |editor, window, cx| {
 6095        editor.fold_creases(
 6096            vec![Crease::simple(
 6097                Point::new(6, 4)..Point::new(7, 4),
 6098                FoldPlaceholder::test(),
 6099            )],
 6100            true,
 6101            window,
 6102            cx,
 6103        );
 6104        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6105            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 6106        });
 6107        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 6108        editor.move_line_up(&MoveLineUp, window, cx);
 6109        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 6110        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 6111    });
 6112}
 6113
 6114#[gpui::test]
 6115fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 6116    init_test(cx, |_| {});
 6117
 6118    let editor = cx.add_window(|window, cx| {
 6119        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6120        build_editor(buffer, window, cx)
 6121    });
 6122    _ = editor.update(cx, |editor, window, cx| {
 6123        let snapshot = editor.buffer.read(cx).snapshot(cx);
 6124        editor.insert_blocks(
 6125            [BlockProperties {
 6126                style: BlockStyle::Fixed,
 6127                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 6128                height: Some(1),
 6129                render: Arc::new(|_| div().into_any()),
 6130                priority: 0,
 6131            }],
 6132            Some(Autoscroll::fit()),
 6133            cx,
 6134        );
 6135        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6136            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 6137        });
 6138        editor.move_line_down(&MoveLineDown, window, cx);
 6139    });
 6140}
 6141
 6142#[gpui::test]
 6143async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 6144    init_test(cx, |_| {});
 6145
 6146    let mut cx = EditorTestContext::new(cx).await;
 6147    cx.set_state(
 6148        &"
 6149            ˇzero
 6150            one
 6151            two
 6152            three
 6153            four
 6154            five
 6155        "
 6156        .unindent(),
 6157    );
 6158
 6159    // Create a four-line block that replaces three lines of text.
 6160    cx.update_editor(|editor, window, cx| {
 6161        let snapshot = editor.snapshot(window, cx);
 6162        let snapshot = &snapshot.buffer_snapshot();
 6163        let placement = BlockPlacement::Replace(
 6164            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 6165        );
 6166        editor.insert_blocks(
 6167            [BlockProperties {
 6168                placement,
 6169                height: Some(4),
 6170                style: BlockStyle::Sticky,
 6171                render: Arc::new(|_| gpui::div().into_any_element()),
 6172                priority: 0,
 6173            }],
 6174            None,
 6175            cx,
 6176        );
 6177    });
 6178
 6179    // Move down so that the cursor touches the block.
 6180    cx.update_editor(|editor, window, cx| {
 6181        editor.move_down(&Default::default(), window, cx);
 6182    });
 6183    cx.assert_editor_state(
 6184        &"
 6185            zero
 6186            «one
 6187            two
 6188            threeˇ»
 6189            four
 6190            five
 6191        "
 6192        .unindent(),
 6193    );
 6194
 6195    // Move down past the block.
 6196    cx.update_editor(|editor, window, cx| {
 6197        editor.move_down(&Default::default(), window, cx);
 6198    });
 6199    cx.assert_editor_state(
 6200        &"
 6201            zero
 6202            one
 6203            two
 6204            three
 6205            ˇfour
 6206            five
 6207        "
 6208        .unindent(),
 6209    );
 6210}
 6211
 6212#[gpui::test]
 6213fn test_transpose(cx: &mut TestAppContext) {
 6214    init_test(cx, |_| {});
 6215
 6216    _ = cx.add_window(|window, cx| {
 6217        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6218        editor.set_style(EditorStyle::default(), window, cx);
 6219        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6220            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6221        });
 6222        editor.transpose(&Default::default(), window, cx);
 6223        assert_eq!(editor.text(cx), "bac");
 6224        assert_eq!(
 6225            editor.selections.ranges(&editor.display_snapshot(cx)),
 6226            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6227        );
 6228
 6229        editor.transpose(&Default::default(), window, cx);
 6230        assert_eq!(editor.text(cx), "bca");
 6231        assert_eq!(
 6232            editor.selections.ranges(&editor.display_snapshot(cx)),
 6233            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6234        );
 6235
 6236        editor.transpose(&Default::default(), window, cx);
 6237        assert_eq!(editor.text(cx), "bac");
 6238        assert_eq!(
 6239            editor.selections.ranges(&editor.display_snapshot(cx)),
 6240            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6241        );
 6242
 6243        editor
 6244    });
 6245
 6246    _ = cx.add_window(|window, cx| {
 6247        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6248        editor.set_style(EditorStyle::default(), window, cx);
 6249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6250            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6251        });
 6252        editor.transpose(&Default::default(), window, cx);
 6253        assert_eq!(editor.text(cx), "acb\nde");
 6254        assert_eq!(
 6255            editor.selections.ranges(&editor.display_snapshot(cx)),
 6256            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6257        );
 6258
 6259        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6260            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6261        });
 6262        editor.transpose(&Default::default(), window, cx);
 6263        assert_eq!(editor.text(cx), "acbd\ne");
 6264        assert_eq!(
 6265            editor.selections.ranges(&editor.display_snapshot(cx)),
 6266            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6267        );
 6268
 6269        editor.transpose(&Default::default(), window, cx);
 6270        assert_eq!(editor.text(cx), "acbde\n");
 6271        assert_eq!(
 6272            editor.selections.ranges(&editor.display_snapshot(cx)),
 6273            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6274        );
 6275
 6276        editor.transpose(&Default::default(), window, cx);
 6277        assert_eq!(editor.text(cx), "acbd\ne");
 6278        assert_eq!(
 6279            editor.selections.ranges(&editor.display_snapshot(cx)),
 6280            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6281        );
 6282
 6283        editor
 6284    });
 6285
 6286    _ = cx.add_window(|window, cx| {
 6287        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6288        editor.set_style(EditorStyle::default(), window, cx);
 6289        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6290            s.select_ranges([
 6291                MultiBufferOffset(1)..MultiBufferOffset(1),
 6292                MultiBufferOffset(2)..MultiBufferOffset(2),
 6293                MultiBufferOffset(4)..MultiBufferOffset(4),
 6294            ])
 6295        });
 6296        editor.transpose(&Default::default(), window, cx);
 6297        assert_eq!(editor.text(cx), "bacd\ne");
 6298        assert_eq!(
 6299            editor.selections.ranges(&editor.display_snapshot(cx)),
 6300            [
 6301                MultiBufferOffset(2)..MultiBufferOffset(2),
 6302                MultiBufferOffset(3)..MultiBufferOffset(3),
 6303                MultiBufferOffset(5)..MultiBufferOffset(5)
 6304            ]
 6305        );
 6306
 6307        editor.transpose(&Default::default(), window, cx);
 6308        assert_eq!(editor.text(cx), "bcade\n");
 6309        assert_eq!(
 6310            editor.selections.ranges(&editor.display_snapshot(cx)),
 6311            [
 6312                MultiBufferOffset(3)..MultiBufferOffset(3),
 6313                MultiBufferOffset(4)..MultiBufferOffset(4),
 6314                MultiBufferOffset(6)..MultiBufferOffset(6)
 6315            ]
 6316        );
 6317
 6318        editor.transpose(&Default::default(), window, cx);
 6319        assert_eq!(editor.text(cx), "bcda\ne");
 6320        assert_eq!(
 6321            editor.selections.ranges(&editor.display_snapshot(cx)),
 6322            [
 6323                MultiBufferOffset(4)..MultiBufferOffset(4),
 6324                MultiBufferOffset(6)..MultiBufferOffset(6)
 6325            ]
 6326        );
 6327
 6328        editor.transpose(&Default::default(), window, cx);
 6329        assert_eq!(editor.text(cx), "bcade\n");
 6330        assert_eq!(
 6331            editor.selections.ranges(&editor.display_snapshot(cx)),
 6332            [
 6333                MultiBufferOffset(4)..MultiBufferOffset(4),
 6334                MultiBufferOffset(6)..MultiBufferOffset(6)
 6335            ]
 6336        );
 6337
 6338        editor.transpose(&Default::default(), window, cx);
 6339        assert_eq!(editor.text(cx), "bcaed\n");
 6340        assert_eq!(
 6341            editor.selections.ranges(&editor.display_snapshot(cx)),
 6342            [
 6343                MultiBufferOffset(5)..MultiBufferOffset(5),
 6344                MultiBufferOffset(6)..MultiBufferOffset(6)
 6345            ]
 6346        );
 6347
 6348        editor
 6349    });
 6350
 6351    _ = cx.add_window(|window, cx| {
 6352        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6353        editor.set_style(EditorStyle::default(), window, cx);
 6354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6355            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6356        });
 6357        editor.transpose(&Default::default(), window, cx);
 6358        assert_eq!(editor.text(cx), "🏀🍐✋");
 6359        assert_eq!(
 6360            editor.selections.ranges(&editor.display_snapshot(cx)),
 6361            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6362        );
 6363
 6364        editor.transpose(&Default::default(), window, cx);
 6365        assert_eq!(editor.text(cx), "🏀✋🍐");
 6366        assert_eq!(
 6367            editor.selections.ranges(&editor.display_snapshot(cx)),
 6368            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6369        );
 6370
 6371        editor.transpose(&Default::default(), window, cx);
 6372        assert_eq!(editor.text(cx), "🏀🍐✋");
 6373        assert_eq!(
 6374            editor.selections.ranges(&editor.display_snapshot(cx)),
 6375            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6376        );
 6377
 6378        editor
 6379    });
 6380}
 6381
 6382#[gpui::test]
 6383async fn test_rewrap(cx: &mut TestAppContext) {
 6384    init_test(cx, |settings| {
 6385        settings.languages.0.extend([
 6386            (
 6387                "Markdown".into(),
 6388                LanguageSettingsContent {
 6389                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6390                    preferred_line_length: Some(40),
 6391                    ..Default::default()
 6392                },
 6393            ),
 6394            (
 6395                "Plain Text".into(),
 6396                LanguageSettingsContent {
 6397                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6398                    preferred_line_length: Some(40),
 6399                    ..Default::default()
 6400                },
 6401            ),
 6402            (
 6403                "C++".into(),
 6404                LanguageSettingsContent {
 6405                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6406                    preferred_line_length: Some(40),
 6407                    ..Default::default()
 6408                },
 6409            ),
 6410            (
 6411                "Python".into(),
 6412                LanguageSettingsContent {
 6413                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6414                    preferred_line_length: Some(40),
 6415                    ..Default::default()
 6416                },
 6417            ),
 6418            (
 6419                "Rust".into(),
 6420                LanguageSettingsContent {
 6421                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6422                    preferred_line_length: Some(40),
 6423                    ..Default::default()
 6424                },
 6425            ),
 6426        ])
 6427    });
 6428
 6429    let mut cx = EditorTestContext::new(cx).await;
 6430
 6431    let cpp_language = Arc::new(Language::new(
 6432        LanguageConfig {
 6433            name: "C++".into(),
 6434            line_comments: vec!["// ".into()],
 6435            ..LanguageConfig::default()
 6436        },
 6437        None,
 6438    ));
 6439    let python_language = Arc::new(Language::new(
 6440        LanguageConfig {
 6441            name: "Python".into(),
 6442            line_comments: vec!["# ".into()],
 6443            ..LanguageConfig::default()
 6444        },
 6445        None,
 6446    ));
 6447    let markdown_language = Arc::new(Language::new(
 6448        LanguageConfig {
 6449            name: "Markdown".into(),
 6450            rewrap_prefixes: vec![
 6451                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6452                regex::Regex::new("[-*+]\\s+").unwrap(),
 6453            ],
 6454            ..LanguageConfig::default()
 6455        },
 6456        None,
 6457    ));
 6458    let rust_language = Arc::new(
 6459        Language::new(
 6460            LanguageConfig {
 6461                name: "Rust".into(),
 6462                line_comments: vec!["// ".into(), "/// ".into()],
 6463                ..LanguageConfig::default()
 6464            },
 6465            Some(tree_sitter_rust::LANGUAGE.into()),
 6466        )
 6467        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6468        .unwrap(),
 6469    );
 6470
 6471    let plaintext_language = Arc::new(Language::new(
 6472        LanguageConfig {
 6473            name: "Plain Text".into(),
 6474            ..LanguageConfig::default()
 6475        },
 6476        None,
 6477    ));
 6478
 6479    // Test basic rewrapping of a long line with a cursor
 6480    assert_rewrap(
 6481        indoc! {"
 6482            // ˇThis is a long comment that needs to be wrapped.
 6483        "},
 6484        indoc! {"
 6485            // ˇThis is a long comment that needs to
 6486            // be wrapped.
 6487        "},
 6488        cpp_language.clone(),
 6489        &mut cx,
 6490    );
 6491
 6492    // Test rewrapping a full selection
 6493    assert_rewrap(
 6494        indoc! {"
 6495            «// This selected long comment needs to be wrapped.ˇ»"
 6496        },
 6497        indoc! {"
 6498            «// This selected long comment needs to
 6499            // be wrapped.ˇ»"
 6500        },
 6501        cpp_language.clone(),
 6502        &mut cx,
 6503    );
 6504
 6505    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6506    assert_rewrap(
 6507        indoc! {"
 6508            // ˇThis is the first line.
 6509            // Thisˇ is the second line.
 6510            // This is the thirdˇ line, all part of one paragraph.
 6511         "},
 6512        indoc! {"
 6513            // ˇThis is the first line. Thisˇ is the
 6514            // second line. This is the thirdˇ line,
 6515            // all part of one paragraph.
 6516         "},
 6517        cpp_language.clone(),
 6518        &mut cx,
 6519    );
 6520
 6521    // Test multiple cursors in different paragraphs trigger separate rewraps
 6522    assert_rewrap(
 6523        indoc! {"
 6524            // ˇThis is the first paragraph, first line.
 6525            // ˇThis is the first paragraph, second line.
 6526
 6527            // ˇThis is the second paragraph, first line.
 6528            // ˇThis is the second paragraph, second line.
 6529        "},
 6530        indoc! {"
 6531            // ˇThis is the first paragraph, first
 6532            // line. ˇThis is the first paragraph,
 6533            // second line.
 6534
 6535            // ˇThis is the second paragraph, first
 6536            // line. ˇThis is the second paragraph,
 6537            // second line.
 6538        "},
 6539        cpp_language.clone(),
 6540        &mut cx,
 6541    );
 6542
 6543    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6544    assert_rewrap(
 6545        indoc! {"
 6546            «// A regular long long comment to be wrapped.
 6547            /// A documentation long comment to be wrapped.ˇ»
 6548          "},
 6549        indoc! {"
 6550            «// A regular long long comment to be
 6551            // wrapped.
 6552            /// A documentation long comment to be
 6553            /// wrapped.ˇ»
 6554          "},
 6555        rust_language.clone(),
 6556        &mut cx,
 6557    );
 6558
 6559    // Test that change in indentation level trigger seperate rewraps
 6560    assert_rewrap(
 6561        indoc! {"
 6562            fn foo() {
 6563                «// This is a long comment at the base indent.
 6564                    // This is a long comment at the next indent.ˇ»
 6565            }
 6566        "},
 6567        indoc! {"
 6568            fn foo() {
 6569                «// This is a long comment at the
 6570                // base indent.
 6571                    // This is a long comment at the
 6572                    // next indent.ˇ»
 6573            }
 6574        "},
 6575        rust_language.clone(),
 6576        &mut cx,
 6577    );
 6578
 6579    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6580    assert_rewrap(
 6581        indoc! {"
 6582            # ˇThis is a long comment using a pound sign.
 6583        "},
 6584        indoc! {"
 6585            # ˇThis is a long comment using a pound
 6586            # sign.
 6587        "},
 6588        python_language,
 6589        &mut cx,
 6590    );
 6591
 6592    // Test rewrapping only affects comments, not code even when selected
 6593    assert_rewrap(
 6594        indoc! {"
 6595            «/// This doc comment is long and should be wrapped.
 6596            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6597        "},
 6598        indoc! {"
 6599            «/// This doc comment is long and should
 6600            /// be wrapped.
 6601            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6602        "},
 6603        rust_language.clone(),
 6604        &mut cx,
 6605    );
 6606
 6607    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6608    assert_rewrap(
 6609        indoc! {"
 6610            # Header
 6611
 6612            A long long long line of markdown text to wrap.ˇ
 6613         "},
 6614        indoc! {"
 6615            # Header
 6616
 6617            A long long long line of markdown text
 6618            to wrap.ˇ
 6619         "},
 6620        markdown_language.clone(),
 6621        &mut cx,
 6622    );
 6623
 6624    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6625    assert_rewrap(
 6626        indoc! {"
 6627            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6628            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6629            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6630        "},
 6631        indoc! {"
 6632            «1. This is a numbered list item that is
 6633               very long and needs to be wrapped
 6634               properly.
 6635            2. This is a numbered list item that is
 6636               very long and needs to be wrapped
 6637               properly.
 6638            - This is an unordered list item that is
 6639              also very long and should not merge
 6640              with the numbered item.ˇ»
 6641        "},
 6642        markdown_language.clone(),
 6643        &mut cx,
 6644    );
 6645
 6646    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6647    assert_rewrap(
 6648        indoc! {"
 6649            «1. This is a numbered list item that is
 6650            very long and needs to be wrapped
 6651            properly.
 6652            2. This is a numbered list item that is
 6653            very long and needs to be wrapped
 6654            properly.
 6655            - This is an unordered list item that is
 6656            also very long and should not merge with
 6657            the numbered item.ˇ»
 6658        "},
 6659        indoc! {"
 6660            «1. This is a numbered list item that is
 6661               very long and needs to be wrapped
 6662               properly.
 6663            2. This is a numbered list item that is
 6664               very long and needs to be wrapped
 6665               properly.
 6666            - This is an unordered list item that is
 6667              also very long and should not merge
 6668              with the numbered item.ˇ»
 6669        "},
 6670        markdown_language.clone(),
 6671        &mut cx,
 6672    );
 6673
 6674    // Test that rewrapping maintain indents even when they already exists.
 6675    assert_rewrap(
 6676        indoc! {"
 6677            «1. This is a numbered list
 6678               item that is very long and needs to be wrapped properly.
 6679            2. This is a numbered list
 6680               item that is very long and needs to be wrapped properly.
 6681            - This is an unordered list item that is also very long and
 6682              should not merge with the numbered item.ˇ»
 6683        "},
 6684        indoc! {"
 6685            «1. This is a numbered list item that is
 6686               very long and needs to be wrapped
 6687               properly.
 6688            2. This is a numbered list item that is
 6689               very long and needs to be wrapped
 6690               properly.
 6691            - This is an unordered list item that is
 6692              also very long and should not merge
 6693              with the numbered item.ˇ»
 6694        "},
 6695        markdown_language,
 6696        &mut cx,
 6697    );
 6698
 6699    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6700    assert_rewrap(
 6701        indoc! {"
 6702            ˇThis is a very long line of plain text that will be wrapped.
 6703        "},
 6704        indoc! {"
 6705            ˇThis is a very long line of plain text
 6706            that will be wrapped.
 6707        "},
 6708        plaintext_language.clone(),
 6709        &mut cx,
 6710    );
 6711
 6712    // Test that non-commented code acts as a paragraph boundary within a selection
 6713    assert_rewrap(
 6714        indoc! {"
 6715               «// This is the first long comment block to be wrapped.
 6716               fn my_func(a: u32);
 6717               // This is the second long comment block to be wrapped.ˇ»
 6718           "},
 6719        indoc! {"
 6720               «// This is the first long comment block
 6721               // to be wrapped.
 6722               fn my_func(a: u32);
 6723               // This is the second long comment block
 6724               // to be wrapped.ˇ»
 6725           "},
 6726        rust_language,
 6727        &mut cx,
 6728    );
 6729
 6730    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6731    assert_rewrap(
 6732        indoc! {"
 6733            «ˇThis is a very long line that will be wrapped.
 6734
 6735            This is another paragraph in the same selection.»
 6736
 6737            «\tThis is a very long indented line that will be wrapped.ˇ»
 6738         "},
 6739        indoc! {"
 6740            «ˇThis is a very long line that will be
 6741            wrapped.
 6742
 6743            This is another paragraph in the same
 6744            selection.»
 6745
 6746            «\tThis is a very long indented line
 6747            \tthat will be wrapped.ˇ»
 6748         "},
 6749        plaintext_language,
 6750        &mut cx,
 6751    );
 6752
 6753    // Test that an empty comment line acts as a paragraph boundary
 6754    assert_rewrap(
 6755        indoc! {"
 6756            // ˇThis is a long comment that will be wrapped.
 6757            //
 6758            // And this is another long comment that will also be wrapped.ˇ
 6759         "},
 6760        indoc! {"
 6761            // ˇThis is a long comment that will be
 6762            // wrapped.
 6763            //
 6764            // And this is another long comment that
 6765            // will also be wrapped.ˇ
 6766         "},
 6767        cpp_language,
 6768        &mut cx,
 6769    );
 6770
 6771    #[track_caller]
 6772    fn assert_rewrap(
 6773        unwrapped_text: &str,
 6774        wrapped_text: &str,
 6775        language: Arc<Language>,
 6776        cx: &mut EditorTestContext,
 6777    ) {
 6778        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6779        cx.set_state(unwrapped_text);
 6780        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6781        cx.assert_editor_state(wrapped_text);
 6782    }
 6783}
 6784
 6785#[gpui::test]
 6786async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6787    init_test(cx, |settings| {
 6788        settings.languages.0.extend([(
 6789            "Rust".into(),
 6790            LanguageSettingsContent {
 6791                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6792                preferred_line_length: Some(40),
 6793                ..Default::default()
 6794            },
 6795        )])
 6796    });
 6797
 6798    let mut cx = EditorTestContext::new(cx).await;
 6799
 6800    let rust_lang = Arc::new(
 6801        Language::new(
 6802            LanguageConfig {
 6803                name: "Rust".into(),
 6804                line_comments: vec!["// ".into()],
 6805                block_comment: Some(BlockCommentConfig {
 6806                    start: "/*".into(),
 6807                    end: "*/".into(),
 6808                    prefix: "* ".into(),
 6809                    tab_size: 1,
 6810                }),
 6811                documentation_comment: Some(BlockCommentConfig {
 6812                    start: "/**".into(),
 6813                    end: "*/".into(),
 6814                    prefix: "* ".into(),
 6815                    tab_size: 1,
 6816                }),
 6817
 6818                ..LanguageConfig::default()
 6819            },
 6820            Some(tree_sitter_rust::LANGUAGE.into()),
 6821        )
 6822        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6823        .unwrap(),
 6824    );
 6825
 6826    // regular block comment
 6827    assert_rewrap(
 6828        indoc! {"
 6829            /*
 6830             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6831             */
 6832            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6833        "},
 6834        indoc! {"
 6835            /*
 6836             *ˇ Lorem ipsum dolor sit amet,
 6837             * consectetur adipiscing elit.
 6838             */
 6839            /*
 6840             *ˇ Lorem ipsum dolor sit amet,
 6841             * consectetur adipiscing elit.
 6842             */
 6843        "},
 6844        rust_lang.clone(),
 6845        &mut cx,
 6846    );
 6847
 6848    // indent is respected
 6849    assert_rewrap(
 6850        indoc! {"
 6851            {}
 6852                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6853        "},
 6854        indoc! {"
 6855            {}
 6856                /*
 6857                 *ˇ Lorem ipsum dolor sit amet,
 6858                 * consectetur adipiscing elit.
 6859                 */
 6860        "},
 6861        rust_lang.clone(),
 6862        &mut cx,
 6863    );
 6864
 6865    // short block comments with inline delimiters
 6866    assert_rewrap(
 6867        indoc! {"
 6868            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6869            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6870             */
 6871            /*
 6872             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6873        "},
 6874        indoc! {"
 6875            /*
 6876             *ˇ Lorem ipsum dolor sit amet,
 6877             * consectetur adipiscing elit.
 6878             */
 6879            /*
 6880             *ˇ Lorem ipsum dolor sit amet,
 6881             * consectetur adipiscing elit.
 6882             */
 6883            /*
 6884             *ˇ Lorem ipsum dolor sit amet,
 6885             * consectetur adipiscing elit.
 6886             */
 6887        "},
 6888        rust_lang.clone(),
 6889        &mut cx,
 6890    );
 6891
 6892    // multiline block comment with inline start/end delimiters
 6893    assert_rewrap(
 6894        indoc! {"
 6895            /*ˇ Lorem ipsum dolor sit amet,
 6896             * consectetur adipiscing elit. */
 6897        "},
 6898        indoc! {"
 6899            /*
 6900             *ˇ Lorem ipsum dolor sit amet,
 6901             * consectetur adipiscing elit.
 6902             */
 6903        "},
 6904        rust_lang.clone(),
 6905        &mut cx,
 6906    );
 6907
 6908    // block comment rewrap still respects paragraph bounds
 6909    assert_rewrap(
 6910        indoc! {"
 6911            /*
 6912             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6913             *
 6914             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6915             */
 6916        "},
 6917        indoc! {"
 6918            /*
 6919             *ˇ Lorem ipsum dolor sit amet,
 6920             * consectetur adipiscing elit.
 6921             *
 6922             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6923             */
 6924        "},
 6925        rust_lang.clone(),
 6926        &mut cx,
 6927    );
 6928
 6929    // documentation comments
 6930    assert_rewrap(
 6931        indoc! {"
 6932            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6933            /**
 6934             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6935             */
 6936        "},
 6937        indoc! {"
 6938            /**
 6939             *ˇ Lorem ipsum dolor sit amet,
 6940             * consectetur adipiscing elit.
 6941             */
 6942            /**
 6943             *ˇ Lorem ipsum dolor sit amet,
 6944             * consectetur adipiscing elit.
 6945             */
 6946        "},
 6947        rust_lang.clone(),
 6948        &mut cx,
 6949    );
 6950
 6951    // different, adjacent comments
 6952    assert_rewrap(
 6953        indoc! {"
 6954            /**
 6955             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6956             */
 6957            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6958            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6959        "},
 6960        indoc! {"
 6961            /**
 6962             *ˇ Lorem ipsum dolor sit amet,
 6963             * consectetur adipiscing elit.
 6964             */
 6965            /*
 6966             *ˇ Lorem ipsum dolor sit amet,
 6967             * consectetur adipiscing elit.
 6968             */
 6969            //ˇ Lorem ipsum dolor sit amet,
 6970            // consectetur adipiscing elit.
 6971        "},
 6972        rust_lang.clone(),
 6973        &mut cx,
 6974    );
 6975
 6976    // selection w/ single short block comment
 6977    assert_rewrap(
 6978        indoc! {"
 6979            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6980        "},
 6981        indoc! {"
 6982            «/*
 6983             * Lorem ipsum dolor sit amet,
 6984             * consectetur adipiscing elit.
 6985             */ˇ»
 6986        "},
 6987        rust_lang.clone(),
 6988        &mut cx,
 6989    );
 6990
 6991    // rewrapping a single comment w/ abutting comments
 6992    assert_rewrap(
 6993        indoc! {"
 6994            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6995            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6996        "},
 6997        indoc! {"
 6998            /*
 6999             * ˇLorem ipsum dolor sit amet,
 7000             * consectetur adipiscing elit.
 7001             */
 7002            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7003        "},
 7004        rust_lang.clone(),
 7005        &mut cx,
 7006    );
 7007
 7008    // selection w/ non-abutting short block comments
 7009    assert_rewrap(
 7010        indoc! {"
 7011            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7012
 7013            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7014        "},
 7015        indoc! {"
 7016            «/*
 7017             * Lorem ipsum dolor sit amet,
 7018             * consectetur adipiscing elit.
 7019             */
 7020
 7021            /*
 7022             * Lorem ipsum dolor sit amet,
 7023             * consectetur adipiscing elit.
 7024             */ˇ»
 7025        "},
 7026        rust_lang.clone(),
 7027        &mut cx,
 7028    );
 7029
 7030    // selection of multiline block comments
 7031    assert_rewrap(
 7032        indoc! {"
 7033            «/* Lorem ipsum dolor sit amet,
 7034             * consectetur adipiscing elit. */ˇ»
 7035        "},
 7036        indoc! {"
 7037            «/*
 7038             * Lorem ipsum dolor sit amet,
 7039             * consectetur adipiscing elit.
 7040             */ˇ»
 7041        "},
 7042        rust_lang.clone(),
 7043        &mut cx,
 7044    );
 7045
 7046    // partial selection of multiline block comments
 7047    assert_rewrap(
 7048        indoc! {"
 7049            «/* Lorem ipsum dolor sit amet,ˇ»
 7050             * consectetur adipiscing elit. */
 7051            /* Lorem ipsum dolor sit amet,
 7052             «* consectetur adipiscing elit. */ˇ»
 7053        "},
 7054        indoc! {"
 7055            «/*
 7056             * Lorem ipsum dolor sit amet,ˇ»
 7057             * consectetur adipiscing elit. */
 7058            /* Lorem ipsum dolor sit amet,
 7059             «* consectetur adipiscing elit.
 7060             */ˇ»
 7061        "},
 7062        rust_lang.clone(),
 7063        &mut cx,
 7064    );
 7065
 7066    // selection w/ abutting short block comments
 7067    // TODO: should not be combined; should rewrap as 2 comments
 7068    assert_rewrap(
 7069        indoc! {"
 7070            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7071            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7072        "},
 7073        // desired behavior:
 7074        // indoc! {"
 7075        //     «/*
 7076        //      * Lorem ipsum dolor sit amet,
 7077        //      * consectetur adipiscing elit.
 7078        //      */
 7079        //     /*
 7080        //      * Lorem ipsum dolor sit amet,
 7081        //      * consectetur adipiscing elit.
 7082        //      */ˇ»
 7083        // "},
 7084        // actual behaviour:
 7085        indoc! {"
 7086            «/*
 7087             * Lorem ipsum dolor sit amet,
 7088             * consectetur adipiscing elit. Lorem
 7089             * ipsum dolor sit amet, consectetur
 7090             * adipiscing elit.
 7091             */ˇ»
 7092        "},
 7093        rust_lang.clone(),
 7094        &mut cx,
 7095    );
 7096
 7097    // TODO: same as above, but with delimiters on separate line
 7098    // assert_rewrap(
 7099    //     indoc! {"
 7100    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7101    //          */
 7102    //         /*
 7103    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7104    //     "},
 7105    //     // desired:
 7106    //     // indoc! {"
 7107    //     //     «/*
 7108    //     //      * Lorem ipsum dolor sit amet,
 7109    //     //      * consectetur adipiscing elit.
 7110    //     //      */
 7111    //     //     /*
 7112    //     //      * Lorem ipsum dolor sit amet,
 7113    //     //      * consectetur adipiscing elit.
 7114    //     //      */ˇ»
 7115    //     // "},
 7116    //     // actual: (but with trailing w/s on the empty lines)
 7117    //     indoc! {"
 7118    //         «/*
 7119    //          * Lorem ipsum dolor sit amet,
 7120    //          * consectetur adipiscing elit.
 7121    //          *
 7122    //          */
 7123    //         /*
 7124    //          *
 7125    //          * Lorem ipsum dolor sit amet,
 7126    //          * consectetur adipiscing elit.
 7127    //          */ˇ»
 7128    //     "},
 7129    //     rust_lang.clone(),
 7130    //     &mut cx,
 7131    // );
 7132
 7133    // TODO these are unhandled edge cases; not correct, just documenting known issues
 7134    assert_rewrap(
 7135        indoc! {"
 7136            /*
 7137             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7138             */
 7139            /*
 7140             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7141            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 7142        "},
 7143        // desired:
 7144        // indoc! {"
 7145        //     /*
 7146        //      *ˇ Lorem ipsum dolor sit amet,
 7147        //      * consectetur adipiscing elit.
 7148        //      */
 7149        //     /*
 7150        //      *ˇ Lorem ipsum dolor sit amet,
 7151        //      * consectetur adipiscing elit.
 7152        //      */
 7153        //     /*
 7154        //      *ˇ Lorem ipsum dolor sit amet
 7155        //      */ /* consectetur adipiscing elit. */
 7156        // "},
 7157        // actual:
 7158        indoc! {"
 7159            /*
 7160             //ˇ Lorem ipsum dolor sit amet,
 7161             // consectetur adipiscing elit.
 7162             */
 7163            /*
 7164             * //ˇ Lorem ipsum dolor sit amet,
 7165             * consectetur adipiscing elit.
 7166             */
 7167            /*
 7168             *ˇ Lorem ipsum dolor sit amet */ /*
 7169             * consectetur adipiscing elit.
 7170             */
 7171        "},
 7172        rust_lang,
 7173        &mut cx,
 7174    );
 7175
 7176    #[track_caller]
 7177    fn assert_rewrap(
 7178        unwrapped_text: &str,
 7179        wrapped_text: &str,
 7180        language: Arc<Language>,
 7181        cx: &mut EditorTestContext,
 7182    ) {
 7183        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7184        cx.set_state(unwrapped_text);
 7185        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7186        cx.assert_editor_state(wrapped_text);
 7187    }
 7188}
 7189
 7190#[gpui::test]
 7191async fn test_hard_wrap(cx: &mut TestAppContext) {
 7192    init_test(cx, |_| {});
 7193    let mut cx = EditorTestContext::new(cx).await;
 7194
 7195    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 7196    cx.update_editor(|editor, _, cx| {
 7197        editor.set_hard_wrap(Some(14), cx);
 7198    });
 7199
 7200    cx.set_state(indoc!(
 7201        "
 7202        one two three ˇ
 7203        "
 7204    ));
 7205    cx.simulate_input("four");
 7206    cx.run_until_parked();
 7207
 7208    cx.assert_editor_state(indoc!(
 7209        "
 7210        one two three
 7211        fourˇ
 7212        "
 7213    ));
 7214
 7215    cx.update_editor(|editor, window, cx| {
 7216        editor.newline(&Default::default(), window, cx);
 7217    });
 7218    cx.run_until_parked();
 7219    cx.assert_editor_state(indoc!(
 7220        "
 7221        one two three
 7222        four
 7223        ˇ
 7224        "
 7225    ));
 7226
 7227    cx.simulate_input("five");
 7228    cx.run_until_parked();
 7229    cx.assert_editor_state(indoc!(
 7230        "
 7231        one two three
 7232        four
 7233        fiveˇ
 7234        "
 7235    ));
 7236
 7237    cx.update_editor(|editor, window, cx| {
 7238        editor.newline(&Default::default(), window, cx);
 7239    });
 7240    cx.run_until_parked();
 7241    cx.simulate_input("# ");
 7242    cx.run_until_parked();
 7243    cx.assert_editor_state(indoc!(
 7244        "
 7245        one two three
 7246        four
 7247        five
 7248        # ˇ
 7249        "
 7250    ));
 7251
 7252    cx.update_editor(|editor, window, cx| {
 7253        editor.newline(&Default::default(), window, cx);
 7254    });
 7255    cx.run_until_parked();
 7256    cx.assert_editor_state(indoc!(
 7257        "
 7258        one two three
 7259        four
 7260        five
 7261        #\x20
 7262 7263        "
 7264    ));
 7265
 7266    cx.simulate_input(" 6");
 7267    cx.run_until_parked();
 7268    cx.assert_editor_state(indoc!(
 7269        "
 7270        one two three
 7271        four
 7272        five
 7273        #
 7274        # 6ˇ
 7275        "
 7276    ));
 7277}
 7278
 7279#[gpui::test]
 7280async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7281    init_test(cx, |_| {});
 7282
 7283    let mut cx = EditorTestContext::new(cx).await;
 7284
 7285    cx.set_state(indoc! {"The quick brownˇ"});
 7286    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7287    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7288
 7289    cx.set_state(indoc! {"The emacs foxˇ"});
 7290    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7291    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7292
 7293    cx.set_state(indoc! {"
 7294        The quick« brownˇ»
 7295        fox jumps overˇ
 7296        the lazy dog"});
 7297    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7298    cx.assert_editor_state(indoc! {"
 7299        The quickˇ
 7300        ˇthe lazy dog"});
 7301
 7302    cx.set_state(indoc! {"
 7303        The quick« brownˇ»
 7304        fox jumps overˇ
 7305        the lazy dog"});
 7306    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7307    cx.assert_editor_state(indoc! {"
 7308        The quickˇ
 7309        fox jumps overˇthe lazy dog"});
 7310
 7311    cx.set_state(indoc! {"
 7312        The quick« brownˇ»
 7313        fox jumps overˇ
 7314        the lazy dog"});
 7315    cx.update_editor(|e, window, cx| {
 7316        e.cut_to_end_of_line(
 7317            &CutToEndOfLine {
 7318                stop_at_newlines: true,
 7319            },
 7320            window,
 7321            cx,
 7322        )
 7323    });
 7324    cx.assert_editor_state(indoc! {"
 7325        The quickˇ
 7326        fox jumps overˇ
 7327        the lazy dog"});
 7328
 7329    cx.set_state(indoc! {"
 7330        The quick« brownˇ»
 7331        fox jumps overˇ
 7332        the lazy dog"});
 7333    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7334    cx.assert_editor_state(indoc! {"
 7335        The quickˇ
 7336        fox jumps overˇthe lazy dog"});
 7337}
 7338
 7339#[gpui::test]
 7340async fn test_clipboard(cx: &mut TestAppContext) {
 7341    init_test(cx, |_| {});
 7342
 7343    let mut cx = EditorTestContext::new(cx).await;
 7344
 7345    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7346    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7347    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7348
 7349    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7350    cx.set_state("two ˇfour ˇsix ˇ");
 7351    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7352    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7353
 7354    // Paste again but with only two cursors. Since the number of cursors doesn't
 7355    // match the number of slices in the clipboard, the entire clipboard text
 7356    // is pasted at each cursor.
 7357    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7358    cx.update_editor(|e, window, cx| {
 7359        e.handle_input("( ", window, cx);
 7360        e.paste(&Paste, window, cx);
 7361        e.handle_input(") ", window, cx);
 7362    });
 7363    cx.assert_editor_state(
 7364        &([
 7365            "( one✅ ",
 7366            "three ",
 7367            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7368            "three ",
 7369            "five ) ˇ",
 7370        ]
 7371        .join("\n")),
 7372    );
 7373
 7374    // Cut with three selections, one of which is full-line.
 7375    cx.set_state(indoc! {"
 7376        1«2ˇ»3
 7377        4ˇ567
 7378        «8ˇ»9"});
 7379    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7380    cx.assert_editor_state(indoc! {"
 7381        1ˇ3
 7382        ˇ9"});
 7383
 7384    // Paste with three selections, noticing how the copied selection that was full-line
 7385    // gets inserted before the second cursor.
 7386    cx.set_state(indoc! {"
 7387        1ˇ3
 7388 7389        «oˇ»ne"});
 7390    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7391    cx.assert_editor_state(indoc! {"
 7392        12ˇ3
 7393        4567
 7394 7395        8ˇne"});
 7396
 7397    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7398    cx.set_state(indoc! {"
 7399        The quick brown
 7400        fox juˇmps over
 7401        the lazy dog"});
 7402    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7403    assert_eq!(
 7404        cx.read_from_clipboard()
 7405            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7406        Some("fox jumps over\n".to_string())
 7407    );
 7408
 7409    // Paste with three selections, noticing how the copied full-line selection is inserted
 7410    // before the empty selections but replaces the selection that is non-empty.
 7411    cx.set_state(indoc! {"
 7412        Tˇhe quick brown
 7413        «foˇ»x jumps over
 7414        tˇhe lazy dog"});
 7415    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7416    cx.assert_editor_state(indoc! {"
 7417        fox jumps over
 7418        Tˇhe quick brown
 7419        fox jumps over
 7420        ˇx jumps over
 7421        fox jumps over
 7422        tˇhe lazy dog"});
 7423}
 7424
 7425#[gpui::test]
 7426async fn test_copy_trim(cx: &mut TestAppContext) {
 7427    init_test(cx, |_| {});
 7428
 7429    let mut cx = EditorTestContext::new(cx).await;
 7430    cx.set_state(
 7431        r#"            «for selection in selections.iter() {
 7432            let mut start = selection.start;
 7433            let mut end = selection.end;
 7434            let is_entire_line = selection.is_empty();
 7435            if is_entire_line {
 7436                start = Point::new(start.row, 0);ˇ»
 7437                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7438            }
 7439        "#,
 7440    );
 7441    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7442    assert_eq!(
 7443        cx.read_from_clipboard()
 7444            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7445        Some(
 7446            "for selection in selections.iter() {
 7447            let mut start = selection.start;
 7448            let mut end = selection.end;
 7449            let is_entire_line = selection.is_empty();
 7450            if is_entire_line {
 7451                start = Point::new(start.row, 0);"
 7452                .to_string()
 7453        ),
 7454        "Regular copying preserves all indentation selected",
 7455    );
 7456    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7457    assert_eq!(
 7458        cx.read_from_clipboard()
 7459            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7460        Some(
 7461            "for selection in selections.iter() {
 7462let mut start = selection.start;
 7463let mut end = selection.end;
 7464let is_entire_line = selection.is_empty();
 7465if is_entire_line {
 7466    start = Point::new(start.row, 0);"
 7467                .to_string()
 7468        ),
 7469        "Copying with stripping should strip all leading whitespaces"
 7470    );
 7471
 7472    cx.set_state(
 7473        r#"       «     for selection in selections.iter() {
 7474            let mut start = selection.start;
 7475            let mut end = selection.end;
 7476            let is_entire_line = selection.is_empty();
 7477            if is_entire_line {
 7478                start = Point::new(start.row, 0);ˇ»
 7479                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7480            }
 7481        "#,
 7482    );
 7483    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7484    assert_eq!(
 7485        cx.read_from_clipboard()
 7486            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7487        Some(
 7488            "     for selection in selections.iter() {
 7489            let mut start = selection.start;
 7490            let mut end = selection.end;
 7491            let is_entire_line = selection.is_empty();
 7492            if is_entire_line {
 7493                start = Point::new(start.row, 0);"
 7494                .to_string()
 7495        ),
 7496        "Regular copying preserves all indentation selected",
 7497    );
 7498    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7499    assert_eq!(
 7500        cx.read_from_clipboard()
 7501            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7502        Some(
 7503            "for selection in selections.iter() {
 7504let mut start = selection.start;
 7505let mut end = selection.end;
 7506let is_entire_line = selection.is_empty();
 7507if is_entire_line {
 7508    start = Point::new(start.row, 0);"
 7509                .to_string()
 7510        ),
 7511        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7512    );
 7513
 7514    cx.set_state(
 7515        r#"       «ˇ     for selection in selections.iter() {
 7516            let mut start = selection.start;
 7517            let mut end = selection.end;
 7518            let is_entire_line = selection.is_empty();
 7519            if is_entire_line {
 7520                start = Point::new(start.row, 0);»
 7521                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7522            }
 7523        "#,
 7524    );
 7525    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7526    assert_eq!(
 7527        cx.read_from_clipboard()
 7528            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7529        Some(
 7530            "     for selection in selections.iter() {
 7531            let mut start = selection.start;
 7532            let mut end = selection.end;
 7533            let is_entire_line = selection.is_empty();
 7534            if is_entire_line {
 7535                start = Point::new(start.row, 0);"
 7536                .to_string()
 7537        ),
 7538        "Regular copying for reverse selection works the same",
 7539    );
 7540    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7541    assert_eq!(
 7542        cx.read_from_clipboard()
 7543            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7544        Some(
 7545            "for selection in selections.iter() {
 7546let mut start = selection.start;
 7547let mut end = selection.end;
 7548let is_entire_line = selection.is_empty();
 7549if is_entire_line {
 7550    start = Point::new(start.row, 0);"
 7551                .to_string()
 7552        ),
 7553        "Copying with stripping for reverse selection works the same"
 7554    );
 7555
 7556    cx.set_state(
 7557        r#"            for selection «in selections.iter() {
 7558            let mut start = selection.start;
 7559            let mut end = selection.end;
 7560            let is_entire_line = selection.is_empty();
 7561            if is_entire_line {
 7562                start = Point::new(start.row, 0);ˇ»
 7563                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7564            }
 7565        "#,
 7566    );
 7567    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7568    assert_eq!(
 7569        cx.read_from_clipboard()
 7570            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7571        Some(
 7572            "in selections.iter() {
 7573            let mut start = selection.start;
 7574            let mut end = selection.end;
 7575            let is_entire_line = selection.is_empty();
 7576            if is_entire_line {
 7577                start = Point::new(start.row, 0);"
 7578                .to_string()
 7579        ),
 7580        "When selecting past the indent, the copying works as usual",
 7581    );
 7582    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7583    assert_eq!(
 7584        cx.read_from_clipboard()
 7585            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7586        Some(
 7587            "in selections.iter() {
 7588            let mut start = selection.start;
 7589            let mut end = selection.end;
 7590            let is_entire_line = selection.is_empty();
 7591            if is_entire_line {
 7592                start = Point::new(start.row, 0);"
 7593                .to_string()
 7594        ),
 7595        "When selecting past the indent, nothing is trimmed"
 7596    );
 7597
 7598    cx.set_state(
 7599        r#"            «for selection in selections.iter() {
 7600            let mut start = selection.start;
 7601
 7602            let mut end = selection.end;
 7603            let is_entire_line = selection.is_empty();
 7604            if is_entire_line {
 7605                start = Point::new(start.row, 0);
 7606ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7607            }
 7608        "#,
 7609    );
 7610    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7611    assert_eq!(
 7612        cx.read_from_clipboard()
 7613            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7614        Some(
 7615            "for selection in selections.iter() {
 7616let mut start = selection.start;
 7617
 7618let mut end = selection.end;
 7619let is_entire_line = selection.is_empty();
 7620if is_entire_line {
 7621    start = Point::new(start.row, 0);
 7622"
 7623            .to_string()
 7624        ),
 7625        "Copying with stripping should ignore empty lines"
 7626    );
 7627}
 7628
 7629#[gpui::test]
 7630async fn test_copy_trim_line_mode(cx: &mut TestAppContext) {
 7631    init_test(cx, |_| {});
 7632
 7633    let mut cx = EditorTestContext::new(cx).await;
 7634
 7635    cx.set_state(indoc! {"
 7636        «    a
 7637            bˇ»
 7638    "});
 7639    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 7640    cx.update_editor(|editor, window, cx| editor.copy_and_trim(&CopyAndTrim, window, cx));
 7641
 7642    assert_eq!(
 7643        cx.read_from_clipboard().and_then(|item| item.text()),
 7644        Some("a\nb\n".to_string())
 7645    );
 7646}
 7647
 7648#[gpui::test]
 7649async fn test_clipboard_line_numbers_from_multibuffer(cx: &mut TestAppContext) {
 7650    init_test(cx, |_| {});
 7651
 7652    let fs = FakeFs::new(cx.executor());
 7653    fs.insert_file(
 7654        path!("/file.txt"),
 7655        "first line\nsecond line\nthird line\nfourth line\nfifth line\n".into(),
 7656    )
 7657    .await;
 7658
 7659    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 7660
 7661    let buffer = project
 7662        .update(cx, |project, cx| {
 7663            project.open_local_buffer(path!("/file.txt"), cx)
 7664        })
 7665        .await
 7666        .unwrap();
 7667
 7668    let multibuffer = cx.new(|cx| {
 7669        let mut multibuffer = MultiBuffer::new(ReadWrite);
 7670        multibuffer.push_excerpts(
 7671            buffer.clone(),
 7672            [ExcerptRange::new(Point::new(2, 0)..Point::new(5, 0))],
 7673            cx,
 7674        );
 7675        multibuffer
 7676    });
 7677
 7678    let (editor, cx) = cx.add_window_view(|window, cx| {
 7679        build_editor_with_project(project.clone(), multibuffer, window, cx)
 7680    });
 7681
 7682    editor.update_in(cx, |editor, window, cx| {
 7683        assert_eq!(editor.text(cx), "third line\nfourth line\nfifth line\n");
 7684
 7685        editor.select_all(&SelectAll, window, cx);
 7686        editor.copy(&Copy, window, cx);
 7687    });
 7688
 7689    let clipboard_selections: Option<Vec<ClipboardSelection>> = cx
 7690        .read_from_clipboard()
 7691        .and_then(|item| item.entries().first().cloned())
 7692        .and_then(|entry| match entry {
 7693            gpui::ClipboardEntry::String(text) => text.metadata_json(),
 7694            _ => None,
 7695        });
 7696
 7697    let selections = clipboard_selections.expect("should have clipboard selections");
 7698    assert_eq!(selections.len(), 1);
 7699    let selection = &selections[0];
 7700    assert_eq!(
 7701        selection.line_range,
 7702        Some(2..=5),
 7703        "line range should be from original file (rows 2-5), not multibuffer rows (0-2)"
 7704    );
 7705}
 7706
 7707#[gpui::test]
 7708async fn test_paste_multiline(cx: &mut TestAppContext) {
 7709    init_test(cx, |_| {});
 7710
 7711    let mut cx = EditorTestContext::new(cx).await;
 7712    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7713
 7714    // Cut an indented block, without the leading whitespace.
 7715    cx.set_state(indoc! {"
 7716        const a: B = (
 7717            c(),
 7718            «d(
 7719                e,
 7720                f
 7721            )ˇ»
 7722        );
 7723    "});
 7724    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7725    cx.assert_editor_state(indoc! {"
 7726        const a: B = (
 7727            c(),
 7728            ˇ
 7729        );
 7730    "});
 7731
 7732    // Paste it at the same position.
 7733    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7734    cx.assert_editor_state(indoc! {"
 7735        const a: B = (
 7736            c(),
 7737            d(
 7738                e,
 7739                f
 7740 7741        );
 7742    "});
 7743
 7744    // Paste it at a line with a lower indent level.
 7745    cx.set_state(indoc! {"
 7746        ˇ
 7747        const a: B = (
 7748            c(),
 7749        );
 7750    "});
 7751    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7752    cx.assert_editor_state(indoc! {"
 7753        d(
 7754            e,
 7755            f
 7756 7757        const a: B = (
 7758            c(),
 7759        );
 7760    "});
 7761
 7762    // Cut an indented block, with the leading whitespace.
 7763    cx.set_state(indoc! {"
 7764        const a: B = (
 7765            c(),
 7766        «    d(
 7767                e,
 7768                f
 7769            )
 7770        ˇ»);
 7771    "});
 7772    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7773    cx.assert_editor_state(indoc! {"
 7774        const a: B = (
 7775            c(),
 7776        ˇ);
 7777    "});
 7778
 7779    // Paste it at the same position.
 7780    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7781    cx.assert_editor_state(indoc! {"
 7782        const a: B = (
 7783            c(),
 7784            d(
 7785                e,
 7786                f
 7787            )
 7788        ˇ);
 7789    "});
 7790
 7791    // Paste it at a line with a higher indent level.
 7792    cx.set_state(indoc! {"
 7793        const a: B = (
 7794            c(),
 7795            d(
 7796                e,
 7797 7798            )
 7799        );
 7800    "});
 7801    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7802    cx.assert_editor_state(indoc! {"
 7803        const a: B = (
 7804            c(),
 7805            d(
 7806                e,
 7807                f    d(
 7808                    e,
 7809                    f
 7810                )
 7811        ˇ
 7812            )
 7813        );
 7814    "});
 7815
 7816    // Copy an indented block, starting mid-line
 7817    cx.set_state(indoc! {"
 7818        const a: B = (
 7819            c(),
 7820            somethin«g(
 7821                e,
 7822                f
 7823            )ˇ»
 7824        );
 7825    "});
 7826    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7827
 7828    // Paste it on a line with a lower indent level
 7829    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7830    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7831    cx.assert_editor_state(indoc! {"
 7832        const a: B = (
 7833            c(),
 7834            something(
 7835                e,
 7836                f
 7837            )
 7838        );
 7839        g(
 7840            e,
 7841            f
 7842"});
 7843}
 7844
 7845#[gpui::test]
 7846async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7847    init_test(cx, |_| {});
 7848
 7849    cx.write_to_clipboard(ClipboardItem::new_string(
 7850        "    d(\n        e\n    );\n".into(),
 7851    ));
 7852
 7853    let mut cx = EditorTestContext::new(cx).await;
 7854    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7855    cx.run_until_parked();
 7856
 7857    cx.set_state(indoc! {"
 7858        fn a() {
 7859            b();
 7860            if c() {
 7861                ˇ
 7862            }
 7863        }
 7864    "});
 7865
 7866    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7867    cx.assert_editor_state(indoc! {"
 7868        fn a() {
 7869            b();
 7870            if c() {
 7871                d(
 7872                    e
 7873                );
 7874        ˇ
 7875            }
 7876        }
 7877    "});
 7878
 7879    cx.set_state(indoc! {"
 7880        fn a() {
 7881            b();
 7882            ˇ
 7883        }
 7884    "});
 7885
 7886    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7887    cx.assert_editor_state(indoc! {"
 7888        fn a() {
 7889            b();
 7890            d(
 7891                e
 7892            );
 7893        ˇ
 7894        }
 7895    "});
 7896}
 7897
 7898#[gpui::test]
 7899fn test_select_all(cx: &mut TestAppContext) {
 7900    init_test(cx, |_| {});
 7901
 7902    let editor = cx.add_window(|window, cx| {
 7903        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7904        build_editor(buffer, window, cx)
 7905    });
 7906    _ = editor.update(cx, |editor, window, cx| {
 7907        editor.select_all(&SelectAll, window, cx);
 7908        assert_eq!(
 7909            display_ranges(editor, cx),
 7910            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7911        );
 7912    });
 7913}
 7914
 7915#[gpui::test]
 7916fn test_select_line(cx: &mut TestAppContext) {
 7917    init_test(cx, |_| {});
 7918
 7919    let editor = cx.add_window(|window, cx| {
 7920        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7921        build_editor(buffer, window, cx)
 7922    });
 7923    _ = editor.update(cx, |editor, window, cx| {
 7924        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7925            s.select_display_ranges([
 7926                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7927                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7928                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7929                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7930            ])
 7931        });
 7932        editor.select_line(&SelectLine, window, cx);
 7933        // Adjacent line selections should NOT merge (only overlapping ones do)
 7934        assert_eq!(
 7935            display_ranges(editor, cx),
 7936            vec![
 7937                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7938                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7939                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7940            ]
 7941        );
 7942    });
 7943
 7944    _ = editor.update(cx, |editor, window, cx| {
 7945        editor.select_line(&SelectLine, window, cx);
 7946        assert_eq!(
 7947            display_ranges(editor, cx),
 7948            vec![
 7949                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7950                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7951            ]
 7952        );
 7953    });
 7954
 7955    _ = editor.update(cx, |editor, window, cx| {
 7956        editor.select_line(&SelectLine, window, cx);
 7957        // Adjacent but not overlapping, so they stay separate
 7958        assert_eq!(
 7959            display_ranges(editor, cx),
 7960            vec![
 7961                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(4), 0),
 7962                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7963            ]
 7964        );
 7965    });
 7966}
 7967
 7968#[gpui::test]
 7969async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7970    init_test(cx, |_| {});
 7971    let mut cx = EditorTestContext::new(cx).await;
 7972
 7973    #[track_caller]
 7974    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7975        cx.set_state(initial_state);
 7976        cx.update_editor(|e, window, cx| {
 7977            e.split_selection_into_lines(&Default::default(), window, cx)
 7978        });
 7979        cx.assert_editor_state(expected_state);
 7980    }
 7981
 7982    // Selection starts and ends at the middle of lines, left-to-right
 7983    test(
 7984        &mut cx,
 7985        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7986        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7987    );
 7988    // Same thing, right-to-left
 7989    test(
 7990        &mut cx,
 7991        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7992        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7993    );
 7994
 7995    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7996    test(
 7997        &mut cx,
 7998        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7999        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 8000    );
 8001    // Same thing, right-to-left
 8002    test(
 8003        &mut cx,
 8004        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 8005        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 8006    );
 8007
 8008    // Whole buffer, left-to-right, last line ends with newline
 8009    test(
 8010        &mut cx,
 8011        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 8012        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 8013    );
 8014    // Same thing, right-to-left
 8015    test(
 8016        &mut cx,
 8017        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 8018        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 8019    );
 8020
 8021    // Starts at the end of a line, ends at the start of another
 8022    test(
 8023        &mut cx,
 8024        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 8025        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 8026    );
 8027}
 8028
 8029#[gpui::test]
 8030async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 8031    init_test(cx, |_| {});
 8032
 8033    let editor = cx.add_window(|window, cx| {
 8034        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 8035        build_editor(buffer, window, cx)
 8036    });
 8037
 8038    // setup
 8039    _ = editor.update(cx, |editor, window, cx| {
 8040        editor.fold_creases(
 8041            vec![
 8042                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 8043                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 8044                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 8045            ],
 8046            true,
 8047            window,
 8048            cx,
 8049        );
 8050        assert_eq!(
 8051            editor.display_text(cx),
 8052            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8053        );
 8054    });
 8055
 8056    _ = editor.update(cx, |editor, window, cx| {
 8057        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8058            s.select_display_ranges([
 8059                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8060                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 8061                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 8062                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 8063            ])
 8064        });
 8065        editor.split_selection_into_lines(&Default::default(), window, cx);
 8066        assert_eq!(
 8067            editor.display_text(cx),
 8068            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8069        );
 8070    });
 8071    EditorTestContext::for_editor(editor, cx)
 8072        .await
 8073        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 8074
 8075    _ = editor.update(cx, |editor, window, cx| {
 8076        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8077            s.select_display_ranges([
 8078                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 8079            ])
 8080        });
 8081        editor.split_selection_into_lines(&Default::default(), window, cx);
 8082        assert_eq!(
 8083            editor.display_text(cx),
 8084            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 8085        );
 8086        assert_eq!(
 8087            display_ranges(editor, cx),
 8088            [
 8089                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 8090                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 8091                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 8092                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 8093                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 8094                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 8095                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 8096            ]
 8097        );
 8098    });
 8099    EditorTestContext::for_editor(editor, cx)
 8100        .await
 8101        .assert_editor_state(
 8102            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 8103        );
 8104}
 8105
 8106#[gpui::test]
 8107async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 8108    init_test(cx, |_| {});
 8109
 8110    let mut cx = EditorTestContext::new(cx).await;
 8111
 8112    cx.set_state(indoc!(
 8113        r#"abc
 8114           defˇghi
 8115
 8116           jk
 8117           nlmo
 8118           "#
 8119    ));
 8120
 8121    cx.update_editor(|editor, window, cx| {
 8122        editor.add_selection_above(&Default::default(), window, cx);
 8123    });
 8124
 8125    cx.assert_editor_state(indoc!(
 8126        r#"abcˇ
 8127           defˇghi
 8128
 8129           jk
 8130           nlmo
 8131           "#
 8132    ));
 8133
 8134    cx.update_editor(|editor, window, cx| {
 8135        editor.add_selection_above(&Default::default(), window, cx);
 8136    });
 8137
 8138    cx.assert_editor_state(indoc!(
 8139        r#"abcˇ
 8140            defˇghi
 8141
 8142            jk
 8143            nlmo
 8144            "#
 8145    ));
 8146
 8147    cx.update_editor(|editor, window, cx| {
 8148        editor.add_selection_below(&Default::default(), window, cx);
 8149    });
 8150
 8151    cx.assert_editor_state(indoc!(
 8152        r#"abc
 8153           defˇghi
 8154
 8155           jk
 8156           nlmo
 8157           "#
 8158    ));
 8159
 8160    cx.update_editor(|editor, window, cx| {
 8161        editor.undo_selection(&Default::default(), window, cx);
 8162    });
 8163
 8164    cx.assert_editor_state(indoc!(
 8165        r#"abcˇ
 8166           defˇghi
 8167
 8168           jk
 8169           nlmo
 8170           "#
 8171    ));
 8172
 8173    cx.update_editor(|editor, window, cx| {
 8174        editor.redo_selection(&Default::default(), window, cx);
 8175    });
 8176
 8177    cx.assert_editor_state(indoc!(
 8178        r#"abc
 8179           defˇghi
 8180
 8181           jk
 8182           nlmo
 8183           "#
 8184    ));
 8185
 8186    cx.update_editor(|editor, window, cx| {
 8187        editor.add_selection_below(&Default::default(), window, cx);
 8188    });
 8189
 8190    cx.assert_editor_state(indoc!(
 8191        r#"abc
 8192           defˇghi
 8193           ˇ
 8194           jk
 8195           nlmo
 8196           "#
 8197    ));
 8198
 8199    cx.update_editor(|editor, window, cx| {
 8200        editor.add_selection_below(&Default::default(), window, cx);
 8201    });
 8202
 8203    cx.assert_editor_state(indoc!(
 8204        r#"abc
 8205           defˇghi
 8206           ˇ
 8207           jkˇ
 8208           nlmo
 8209           "#
 8210    ));
 8211
 8212    cx.update_editor(|editor, window, cx| {
 8213        editor.add_selection_below(&Default::default(), window, cx);
 8214    });
 8215
 8216    cx.assert_editor_state(indoc!(
 8217        r#"abc
 8218           defˇghi
 8219           ˇ
 8220           jkˇ
 8221           nlmˇo
 8222           "#
 8223    ));
 8224
 8225    cx.update_editor(|editor, window, cx| {
 8226        editor.add_selection_below(&Default::default(), window, cx);
 8227    });
 8228
 8229    cx.assert_editor_state(indoc!(
 8230        r#"abc
 8231           defˇghi
 8232           ˇ
 8233           jkˇ
 8234           nlmˇo
 8235           ˇ"#
 8236    ));
 8237
 8238    // change selections
 8239    cx.set_state(indoc!(
 8240        r#"abc
 8241           def«ˇg»hi
 8242
 8243           jk
 8244           nlmo
 8245           "#
 8246    ));
 8247
 8248    cx.update_editor(|editor, window, cx| {
 8249        editor.add_selection_below(&Default::default(), window, cx);
 8250    });
 8251
 8252    cx.assert_editor_state(indoc!(
 8253        r#"abc
 8254           def«ˇg»hi
 8255
 8256           jk
 8257           nlm«ˇo»
 8258           "#
 8259    ));
 8260
 8261    cx.update_editor(|editor, window, cx| {
 8262        editor.add_selection_below(&Default::default(), window, cx);
 8263    });
 8264
 8265    cx.assert_editor_state(indoc!(
 8266        r#"abc
 8267           def«ˇg»hi
 8268
 8269           jk
 8270           nlm«ˇo»
 8271           "#
 8272    ));
 8273
 8274    cx.update_editor(|editor, window, cx| {
 8275        editor.add_selection_above(&Default::default(), window, cx);
 8276    });
 8277
 8278    cx.assert_editor_state(indoc!(
 8279        r#"abc
 8280           def«ˇg»hi
 8281
 8282           jk
 8283           nlmo
 8284           "#
 8285    ));
 8286
 8287    cx.update_editor(|editor, window, cx| {
 8288        editor.add_selection_above(&Default::default(), window, cx);
 8289    });
 8290
 8291    cx.assert_editor_state(indoc!(
 8292        r#"abc
 8293           def«ˇg»hi
 8294
 8295           jk
 8296           nlmo
 8297           "#
 8298    ));
 8299
 8300    // Change selections again
 8301    cx.set_state(indoc!(
 8302        r#"a«bc
 8303           defgˇ»hi
 8304
 8305           jk
 8306           nlmo
 8307           "#
 8308    ));
 8309
 8310    cx.update_editor(|editor, window, cx| {
 8311        editor.add_selection_below(&Default::default(), window, cx);
 8312    });
 8313
 8314    cx.assert_editor_state(indoc!(
 8315        r#"a«bcˇ»
 8316           d«efgˇ»hi
 8317
 8318           j«kˇ»
 8319           nlmo
 8320           "#
 8321    ));
 8322
 8323    cx.update_editor(|editor, window, cx| {
 8324        editor.add_selection_below(&Default::default(), window, cx);
 8325    });
 8326    cx.assert_editor_state(indoc!(
 8327        r#"a«bcˇ»
 8328           d«efgˇ»hi
 8329
 8330           j«kˇ»
 8331           n«lmoˇ»
 8332           "#
 8333    ));
 8334    cx.update_editor(|editor, window, cx| {
 8335        editor.add_selection_above(&Default::default(), window, cx);
 8336    });
 8337
 8338    cx.assert_editor_state(indoc!(
 8339        r#"a«bcˇ»
 8340           d«efgˇ»hi
 8341
 8342           j«kˇ»
 8343           nlmo
 8344           "#
 8345    ));
 8346
 8347    // Change selections again
 8348    cx.set_state(indoc!(
 8349        r#"abc
 8350           d«ˇefghi
 8351
 8352           jk
 8353           nlm»o
 8354           "#
 8355    ));
 8356
 8357    cx.update_editor(|editor, window, cx| {
 8358        editor.add_selection_above(&Default::default(), window, cx);
 8359    });
 8360
 8361    cx.assert_editor_state(indoc!(
 8362        r#"a«ˇbc»
 8363           d«ˇef»ghi
 8364
 8365           j«ˇk»
 8366           n«ˇlm»o
 8367           "#
 8368    ));
 8369
 8370    cx.update_editor(|editor, window, cx| {
 8371        editor.add_selection_below(&Default::default(), window, cx);
 8372    });
 8373
 8374    cx.assert_editor_state(indoc!(
 8375        r#"abc
 8376           d«ˇef»ghi
 8377
 8378           j«ˇk»
 8379           n«ˇlm»o
 8380           "#
 8381    ));
 8382
 8383    // Assert that the oldest selection's goal column is used when adding more
 8384    // selections, not the most recently added selection's actual column.
 8385    cx.set_state(indoc! {"
 8386        foo bar bazˇ
 8387        foo
 8388        foo bar
 8389    "});
 8390
 8391    cx.update_editor(|editor, window, cx| {
 8392        editor.add_selection_below(
 8393            &AddSelectionBelow {
 8394                skip_soft_wrap: true,
 8395            },
 8396            window,
 8397            cx,
 8398        );
 8399    });
 8400
 8401    cx.assert_editor_state(indoc! {"
 8402        foo bar bazˇ
 8403        fooˇ
 8404        foo bar
 8405    "});
 8406
 8407    cx.update_editor(|editor, window, cx| {
 8408        editor.add_selection_below(
 8409            &AddSelectionBelow {
 8410                skip_soft_wrap: true,
 8411            },
 8412            window,
 8413            cx,
 8414        );
 8415    });
 8416
 8417    cx.assert_editor_state(indoc! {"
 8418        foo bar bazˇ
 8419        fooˇ
 8420        foo barˇ
 8421    "});
 8422
 8423    cx.set_state(indoc! {"
 8424        foo bar baz
 8425        foo
 8426        foo barˇ
 8427    "});
 8428
 8429    cx.update_editor(|editor, window, cx| {
 8430        editor.add_selection_above(
 8431            &AddSelectionAbove {
 8432                skip_soft_wrap: true,
 8433            },
 8434            window,
 8435            cx,
 8436        );
 8437    });
 8438
 8439    cx.assert_editor_state(indoc! {"
 8440        foo bar baz
 8441        fooˇ
 8442        foo barˇ
 8443    "});
 8444
 8445    cx.update_editor(|editor, window, cx| {
 8446        editor.add_selection_above(
 8447            &AddSelectionAbove {
 8448                skip_soft_wrap: true,
 8449            },
 8450            window,
 8451            cx,
 8452        );
 8453    });
 8454
 8455    cx.assert_editor_state(indoc! {"
 8456        foo barˇ baz
 8457        fooˇ
 8458        foo barˇ
 8459    "});
 8460}
 8461
 8462#[gpui::test]
 8463async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8464    init_test(cx, |_| {});
 8465    let mut cx = EditorTestContext::new(cx).await;
 8466
 8467    cx.set_state(indoc!(
 8468        r#"line onˇe
 8469           liˇne two
 8470           line three
 8471           line four"#
 8472    ));
 8473
 8474    cx.update_editor(|editor, window, cx| {
 8475        editor.add_selection_below(&Default::default(), window, cx);
 8476    });
 8477
 8478    // test multiple cursors expand in the same direction
 8479    cx.assert_editor_state(indoc!(
 8480        r#"line onˇe
 8481           liˇne twˇo
 8482           liˇne three
 8483           line four"#
 8484    ));
 8485
 8486    cx.update_editor(|editor, window, cx| {
 8487        editor.add_selection_below(&Default::default(), window, cx);
 8488    });
 8489
 8490    cx.update_editor(|editor, window, cx| {
 8491        editor.add_selection_below(&Default::default(), window, cx);
 8492    });
 8493
 8494    // test multiple cursors expand below overflow
 8495    cx.assert_editor_state(indoc!(
 8496        r#"line onˇe
 8497           liˇne twˇo
 8498           liˇne thˇree
 8499           liˇne foˇur"#
 8500    ));
 8501
 8502    cx.update_editor(|editor, window, cx| {
 8503        editor.add_selection_above(&Default::default(), window, cx);
 8504    });
 8505
 8506    // test multiple cursors retrieves back correctly
 8507    cx.assert_editor_state(indoc!(
 8508        r#"line onˇe
 8509           liˇne twˇo
 8510           liˇne thˇree
 8511           line four"#
 8512    ));
 8513
 8514    cx.update_editor(|editor, window, cx| {
 8515        editor.add_selection_above(&Default::default(), window, cx);
 8516    });
 8517
 8518    cx.update_editor(|editor, window, cx| {
 8519        editor.add_selection_above(&Default::default(), window, cx);
 8520    });
 8521
 8522    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8523    cx.assert_editor_state(indoc!(
 8524        r#"liˇne onˇe
 8525           liˇne two
 8526           line three
 8527           line four"#
 8528    ));
 8529
 8530    cx.update_editor(|editor, window, cx| {
 8531        editor.undo_selection(&Default::default(), window, cx);
 8532    });
 8533
 8534    // test undo
 8535    cx.assert_editor_state(indoc!(
 8536        r#"line onˇe
 8537           liˇne twˇo
 8538           line three
 8539           line four"#
 8540    ));
 8541
 8542    cx.update_editor(|editor, window, cx| {
 8543        editor.redo_selection(&Default::default(), window, cx);
 8544    });
 8545
 8546    // test redo
 8547    cx.assert_editor_state(indoc!(
 8548        r#"liˇne onˇe
 8549           liˇne two
 8550           line three
 8551           line four"#
 8552    ));
 8553
 8554    cx.set_state(indoc!(
 8555        r#"abcd
 8556           ef«ghˇ»
 8557           ijkl
 8558           «mˇ»nop"#
 8559    ));
 8560
 8561    cx.update_editor(|editor, window, cx| {
 8562        editor.add_selection_above(&Default::default(), window, cx);
 8563    });
 8564
 8565    // test multiple selections expand in the same direction
 8566    cx.assert_editor_state(indoc!(
 8567        r#"ab«cdˇ»
 8568           ef«ghˇ»
 8569           «iˇ»jkl
 8570           «mˇ»nop"#
 8571    ));
 8572
 8573    cx.update_editor(|editor, window, cx| {
 8574        editor.add_selection_above(&Default::default(), window, cx);
 8575    });
 8576
 8577    // test multiple selection upward overflow
 8578    cx.assert_editor_state(indoc!(
 8579        r#"ab«cdˇ»
 8580           «eˇ»f«ghˇ»
 8581           «iˇ»jkl
 8582           «mˇ»nop"#
 8583    ));
 8584
 8585    cx.update_editor(|editor, window, cx| {
 8586        editor.add_selection_below(&Default::default(), window, cx);
 8587    });
 8588
 8589    // test multiple selection retrieves back correctly
 8590    cx.assert_editor_state(indoc!(
 8591        r#"abcd
 8592           ef«ghˇ»
 8593           «iˇ»jkl
 8594           «mˇ»nop"#
 8595    ));
 8596
 8597    cx.update_editor(|editor, window, cx| {
 8598        editor.add_selection_below(&Default::default(), window, cx);
 8599    });
 8600
 8601    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8602    cx.assert_editor_state(indoc!(
 8603        r#"abcd
 8604           ef«ghˇ»
 8605           ij«klˇ»
 8606           «mˇ»nop"#
 8607    ));
 8608
 8609    cx.update_editor(|editor, window, cx| {
 8610        editor.undo_selection(&Default::default(), window, cx);
 8611    });
 8612
 8613    // test undo
 8614    cx.assert_editor_state(indoc!(
 8615        r#"abcd
 8616           ef«ghˇ»
 8617           «iˇ»jkl
 8618           «mˇ»nop"#
 8619    ));
 8620
 8621    cx.update_editor(|editor, window, cx| {
 8622        editor.redo_selection(&Default::default(), window, cx);
 8623    });
 8624
 8625    // test redo
 8626    cx.assert_editor_state(indoc!(
 8627        r#"abcd
 8628           ef«ghˇ»
 8629           ij«klˇ»
 8630           «mˇ»nop"#
 8631    ));
 8632}
 8633
 8634#[gpui::test]
 8635async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8636    init_test(cx, |_| {});
 8637    let mut cx = EditorTestContext::new(cx).await;
 8638
 8639    cx.set_state(indoc!(
 8640        r#"line onˇe
 8641           liˇne two
 8642           line three
 8643           line four"#
 8644    ));
 8645
 8646    cx.update_editor(|editor, window, cx| {
 8647        editor.add_selection_below(&Default::default(), window, cx);
 8648        editor.add_selection_below(&Default::default(), window, cx);
 8649        editor.add_selection_below(&Default::default(), window, cx);
 8650    });
 8651
 8652    // initial state with two multi cursor groups
 8653    cx.assert_editor_state(indoc!(
 8654        r#"line onˇe
 8655           liˇne twˇo
 8656           liˇne thˇree
 8657           liˇne foˇur"#
 8658    ));
 8659
 8660    // add single cursor in middle - simulate opt click
 8661    cx.update_editor(|editor, window, cx| {
 8662        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8663        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8664        editor.end_selection(window, cx);
 8665    });
 8666
 8667    cx.assert_editor_state(indoc!(
 8668        r#"line onˇe
 8669           liˇne twˇo
 8670           liˇneˇ thˇree
 8671           liˇne foˇur"#
 8672    ));
 8673
 8674    cx.update_editor(|editor, window, cx| {
 8675        editor.add_selection_above(&Default::default(), window, cx);
 8676    });
 8677
 8678    // test new added selection expands above and existing selection shrinks
 8679    cx.assert_editor_state(indoc!(
 8680        r#"line onˇe
 8681           liˇneˇ twˇo
 8682           liˇneˇ thˇree
 8683           line four"#
 8684    ));
 8685
 8686    cx.update_editor(|editor, window, cx| {
 8687        editor.add_selection_above(&Default::default(), window, cx);
 8688    });
 8689
 8690    // test new added selection expands above and existing selection shrinks
 8691    cx.assert_editor_state(indoc!(
 8692        r#"lineˇ onˇe
 8693           liˇneˇ twˇo
 8694           lineˇ three
 8695           line four"#
 8696    ));
 8697
 8698    // intial state with two selection groups
 8699    cx.set_state(indoc!(
 8700        r#"abcd
 8701           ef«ghˇ»
 8702           ijkl
 8703           «mˇ»nop"#
 8704    ));
 8705
 8706    cx.update_editor(|editor, window, cx| {
 8707        editor.add_selection_above(&Default::default(), window, cx);
 8708        editor.add_selection_above(&Default::default(), window, cx);
 8709    });
 8710
 8711    cx.assert_editor_state(indoc!(
 8712        r#"ab«cdˇ»
 8713           «eˇ»f«ghˇ»
 8714           «iˇ»jkl
 8715           «mˇ»nop"#
 8716    ));
 8717
 8718    // add single selection in middle - simulate opt drag
 8719    cx.update_editor(|editor, window, cx| {
 8720        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8721        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8722        editor.update_selection(
 8723            DisplayPoint::new(DisplayRow(2), 4),
 8724            0,
 8725            gpui::Point::<f32>::default(),
 8726            window,
 8727            cx,
 8728        );
 8729        editor.end_selection(window, cx);
 8730    });
 8731
 8732    cx.assert_editor_state(indoc!(
 8733        r#"ab«cdˇ»
 8734           «eˇ»f«ghˇ»
 8735           «iˇ»jk«lˇ»
 8736           «mˇ»nop"#
 8737    ));
 8738
 8739    cx.update_editor(|editor, window, cx| {
 8740        editor.add_selection_below(&Default::default(), window, cx);
 8741    });
 8742
 8743    // test new added selection expands below, others shrinks from above
 8744    cx.assert_editor_state(indoc!(
 8745        r#"abcd
 8746           ef«ghˇ»
 8747           «iˇ»jk«lˇ»
 8748           «mˇ»no«pˇ»"#
 8749    ));
 8750}
 8751
 8752#[gpui::test]
 8753async fn test_select_next(cx: &mut TestAppContext) {
 8754    init_test(cx, |_| {});
 8755    let mut cx = EditorTestContext::new(cx).await;
 8756
 8757    // Enable case sensitive search.
 8758    update_test_editor_settings(&mut cx, |settings| {
 8759        let mut search_settings = SearchSettingsContent::default();
 8760        search_settings.case_sensitive = Some(true);
 8761        settings.search = Some(search_settings);
 8762    });
 8763
 8764    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8765
 8766    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8767        .unwrap();
 8768    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8769
 8770    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8771        .unwrap();
 8772    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8773
 8774    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8775    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8776
 8777    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8778    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8779
 8780    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8781        .unwrap();
 8782    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8783
 8784    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8785        .unwrap();
 8786    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8787
 8788    // Test selection direction should be preserved
 8789    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8790
 8791    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8792        .unwrap();
 8793    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8794
 8795    // Test case sensitivity
 8796    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8797    cx.update_editor(|e, window, cx| {
 8798        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8799    });
 8800    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8801
 8802    // Disable case sensitive search.
 8803    update_test_editor_settings(&mut cx, |settings| {
 8804        let mut search_settings = SearchSettingsContent::default();
 8805        search_settings.case_sensitive = Some(false);
 8806        settings.search = Some(search_settings);
 8807    });
 8808
 8809    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8810    cx.update_editor(|e, window, cx| {
 8811        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8812        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8813    });
 8814    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8815}
 8816
 8817#[gpui::test]
 8818async fn test_select_all_matches(cx: &mut TestAppContext) {
 8819    init_test(cx, |_| {});
 8820    let mut cx = EditorTestContext::new(cx).await;
 8821
 8822    // Enable case sensitive search.
 8823    update_test_editor_settings(&mut cx, |settings| {
 8824        let mut search_settings = SearchSettingsContent::default();
 8825        search_settings.case_sensitive = Some(true);
 8826        settings.search = Some(search_settings);
 8827    });
 8828
 8829    // Test caret-only selections
 8830    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8831    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8832        .unwrap();
 8833    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8834
 8835    // Test left-to-right selections
 8836    cx.set_state("abc\n«abcˇ»\nabc");
 8837    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8838        .unwrap();
 8839    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8840
 8841    // Test right-to-left selections
 8842    cx.set_state("abc\n«ˇabc»\nabc");
 8843    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8844        .unwrap();
 8845    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8846
 8847    // Test selecting whitespace with caret selection
 8848    cx.set_state("abc\nˇ   abc\nabc");
 8849    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8850        .unwrap();
 8851    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8852
 8853    // Test selecting whitespace with left-to-right selection
 8854    cx.set_state("abc\n«ˇ  »abc\nabc");
 8855    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8856        .unwrap();
 8857    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8858
 8859    // Test no matches with right-to-left selection
 8860    cx.set_state("abc\n«  ˇ»abc\nabc");
 8861    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8862        .unwrap();
 8863    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8864
 8865    // Test with a single word and clip_at_line_ends=true (#29823)
 8866    cx.set_state("aˇbc");
 8867    cx.update_editor(|e, window, cx| {
 8868        e.set_clip_at_line_ends(true, cx);
 8869        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8870        e.set_clip_at_line_ends(false, cx);
 8871    });
 8872    cx.assert_editor_state("«abcˇ»");
 8873
 8874    // Test case sensitivity
 8875    cx.set_state("fˇoo\nFOO\nFoo");
 8876    cx.update_editor(|e, window, cx| {
 8877        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8878    });
 8879    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8880
 8881    // Disable case sensitive search.
 8882    update_test_editor_settings(&mut cx, |settings| {
 8883        let mut search_settings = SearchSettingsContent::default();
 8884        search_settings.case_sensitive = Some(false);
 8885        settings.search = Some(search_settings);
 8886    });
 8887
 8888    cx.set_state("fˇoo\nFOO\nFoo");
 8889    cx.update_editor(|e, window, cx| {
 8890        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8891    });
 8892    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8893}
 8894
 8895#[gpui::test]
 8896async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8897    init_test(cx, |_| {});
 8898
 8899    let mut cx = EditorTestContext::new(cx).await;
 8900
 8901    let large_body_1 = "\nd".repeat(200);
 8902    let large_body_2 = "\ne".repeat(200);
 8903
 8904    cx.set_state(&format!(
 8905        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8906    ));
 8907    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8908        let scroll_position = editor.scroll_position(cx);
 8909        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8910        scroll_position
 8911    });
 8912
 8913    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8914        .unwrap();
 8915    cx.assert_editor_state(&format!(
 8916        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8917    ));
 8918    let scroll_position_after_selection =
 8919        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8920    assert_eq!(
 8921        initial_scroll_position, scroll_position_after_selection,
 8922        "Scroll position should not change after selecting all matches"
 8923    );
 8924}
 8925
 8926#[gpui::test]
 8927async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8928    init_test(cx, |_| {});
 8929
 8930    let mut cx = EditorLspTestContext::new_rust(
 8931        lsp::ServerCapabilities {
 8932            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8933            ..Default::default()
 8934        },
 8935        cx,
 8936    )
 8937    .await;
 8938
 8939    cx.set_state(indoc! {"
 8940        line 1
 8941        line 2
 8942        linˇe 3
 8943        line 4
 8944        line 5
 8945    "});
 8946
 8947    // Make an edit
 8948    cx.update_editor(|editor, window, cx| {
 8949        editor.handle_input("X", window, cx);
 8950    });
 8951
 8952    // Move cursor to a different position
 8953    cx.update_editor(|editor, window, cx| {
 8954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8955            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8956        });
 8957    });
 8958
 8959    cx.assert_editor_state(indoc! {"
 8960        line 1
 8961        line 2
 8962        linXe 3
 8963        line 4
 8964        liˇne 5
 8965    "});
 8966
 8967    cx.lsp
 8968        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8969            Ok(Some(vec![lsp::TextEdit::new(
 8970                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8971                "PREFIX ".to_string(),
 8972            )]))
 8973        });
 8974
 8975    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8976        .unwrap()
 8977        .await
 8978        .unwrap();
 8979
 8980    cx.assert_editor_state(indoc! {"
 8981        PREFIX line 1
 8982        line 2
 8983        linXe 3
 8984        line 4
 8985        liˇne 5
 8986    "});
 8987
 8988    // Undo formatting
 8989    cx.update_editor(|editor, window, cx| {
 8990        editor.undo(&Default::default(), window, cx);
 8991    });
 8992
 8993    // Verify cursor moved back to position after edit
 8994    cx.assert_editor_state(indoc! {"
 8995        line 1
 8996        line 2
 8997        linXˇe 3
 8998        line 4
 8999        line 5
 9000    "});
 9001}
 9002
 9003#[gpui::test]
 9004async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 9005    init_test(cx, |_| {});
 9006
 9007    let mut cx = EditorTestContext::new(cx).await;
 9008
 9009    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 9010    cx.update_editor(|editor, window, cx| {
 9011        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 9012    });
 9013
 9014    cx.set_state(indoc! {"
 9015        line 1
 9016        line 2
 9017        linˇe 3
 9018        line 4
 9019        line 5
 9020        line 6
 9021        line 7
 9022        line 8
 9023        line 9
 9024        line 10
 9025    "});
 9026
 9027    let snapshot = cx.buffer_snapshot();
 9028    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 9029
 9030    cx.update(|_, cx| {
 9031        provider.update(cx, |provider, _| {
 9032            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
 9033                id: None,
 9034                edits: vec![(edit_position..edit_position, "X".into())],
 9035                cursor_position: None,
 9036                edit_preview: None,
 9037            }))
 9038        })
 9039    });
 9040
 9041    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 9042    cx.update_editor(|editor, window, cx| {
 9043        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 9044    });
 9045
 9046    cx.assert_editor_state(indoc! {"
 9047        line 1
 9048        line 2
 9049        lineXˇ 3
 9050        line 4
 9051        line 5
 9052        line 6
 9053        line 7
 9054        line 8
 9055        line 9
 9056        line 10
 9057    "});
 9058
 9059    cx.update_editor(|editor, window, cx| {
 9060        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9061            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 9062        });
 9063    });
 9064
 9065    cx.assert_editor_state(indoc! {"
 9066        line 1
 9067        line 2
 9068        lineX 3
 9069        line 4
 9070        line 5
 9071        line 6
 9072        line 7
 9073        line 8
 9074        line 9
 9075        liˇne 10
 9076    "});
 9077
 9078    cx.update_editor(|editor, window, cx| {
 9079        editor.undo(&Default::default(), window, cx);
 9080    });
 9081
 9082    cx.assert_editor_state(indoc! {"
 9083        line 1
 9084        line 2
 9085        lineˇ 3
 9086        line 4
 9087        line 5
 9088        line 6
 9089        line 7
 9090        line 8
 9091        line 9
 9092        line 10
 9093    "});
 9094}
 9095
 9096#[gpui::test]
 9097async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 9098    init_test(cx, |_| {});
 9099
 9100    let mut cx = EditorTestContext::new(cx).await;
 9101    cx.set_state(
 9102        r#"let foo = 2;
 9103lˇet foo = 2;
 9104let fooˇ = 2;
 9105let foo = 2;
 9106let foo = ˇ2;"#,
 9107    );
 9108
 9109    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9110        .unwrap();
 9111    cx.assert_editor_state(
 9112        r#"let foo = 2;
 9113«letˇ» foo = 2;
 9114let «fooˇ» = 2;
 9115let foo = 2;
 9116let foo = «2ˇ»;"#,
 9117    );
 9118
 9119    // noop for multiple selections with different contents
 9120    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9121        .unwrap();
 9122    cx.assert_editor_state(
 9123        r#"let foo = 2;
 9124«letˇ» foo = 2;
 9125let «fooˇ» = 2;
 9126let foo = 2;
 9127let foo = «2ˇ»;"#,
 9128    );
 9129
 9130    // Test last selection direction should be preserved
 9131    cx.set_state(
 9132        r#"let foo = 2;
 9133let foo = 2;
 9134let «fooˇ» = 2;
 9135let «ˇfoo» = 2;
 9136let foo = 2;"#,
 9137    );
 9138
 9139    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9140        .unwrap();
 9141    cx.assert_editor_state(
 9142        r#"let foo = 2;
 9143let foo = 2;
 9144let «fooˇ» = 2;
 9145let «ˇfoo» = 2;
 9146let «ˇfoo» = 2;"#,
 9147    );
 9148}
 9149
 9150#[gpui::test]
 9151async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 9152    init_test(cx, |_| {});
 9153
 9154    let mut cx =
 9155        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 9156
 9157    cx.assert_editor_state(indoc! {"
 9158        ˇbbb
 9159        ccc
 9160
 9161        bbb
 9162        ccc
 9163        "});
 9164    cx.dispatch_action(SelectPrevious::default());
 9165    cx.assert_editor_state(indoc! {"
 9166                «bbbˇ»
 9167                ccc
 9168
 9169                bbb
 9170                ccc
 9171                "});
 9172    cx.dispatch_action(SelectPrevious::default());
 9173    cx.assert_editor_state(indoc! {"
 9174                «bbbˇ»
 9175                ccc
 9176
 9177                «bbbˇ»
 9178                ccc
 9179                "});
 9180}
 9181
 9182#[gpui::test]
 9183async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 9184    init_test(cx, |_| {});
 9185
 9186    let mut cx = EditorTestContext::new(cx).await;
 9187    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 9188
 9189    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9190        .unwrap();
 9191    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9192
 9193    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9194        .unwrap();
 9195    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9196
 9197    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9198    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9199
 9200    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9201    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9202
 9203    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9204        .unwrap();
 9205    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 9206
 9207    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9208        .unwrap();
 9209    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9210}
 9211
 9212#[gpui::test]
 9213async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 9214    init_test(cx, |_| {});
 9215
 9216    let mut cx = EditorTestContext::new(cx).await;
 9217    cx.set_state("");
 9218
 9219    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9220        .unwrap();
 9221    cx.assert_editor_state("«aˇ»");
 9222    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9223        .unwrap();
 9224    cx.assert_editor_state("«aˇ»");
 9225}
 9226
 9227#[gpui::test]
 9228async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 9229    init_test(cx, |_| {});
 9230
 9231    let mut cx = EditorTestContext::new(cx).await;
 9232    cx.set_state(
 9233        r#"let foo = 2;
 9234lˇet foo = 2;
 9235let fooˇ = 2;
 9236let foo = 2;
 9237let foo = ˇ2;"#,
 9238    );
 9239
 9240    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9241        .unwrap();
 9242    cx.assert_editor_state(
 9243        r#"let foo = 2;
 9244«letˇ» foo = 2;
 9245let «fooˇ» = 2;
 9246let foo = 2;
 9247let foo = «2ˇ»;"#,
 9248    );
 9249
 9250    // noop for multiple selections with different contents
 9251    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9252        .unwrap();
 9253    cx.assert_editor_state(
 9254        r#"let foo = 2;
 9255«letˇ» foo = 2;
 9256let «fooˇ» = 2;
 9257let foo = 2;
 9258let foo = «2ˇ»;"#,
 9259    );
 9260}
 9261
 9262#[gpui::test]
 9263async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 9264    init_test(cx, |_| {});
 9265    let mut cx = EditorTestContext::new(cx).await;
 9266
 9267    // Enable case sensitive search.
 9268    update_test_editor_settings(&mut cx, |settings| {
 9269        let mut search_settings = SearchSettingsContent::default();
 9270        search_settings.case_sensitive = Some(true);
 9271        settings.search = Some(search_settings);
 9272    });
 9273
 9274    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9275
 9276    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9277        .unwrap();
 9278    // selection direction is preserved
 9279    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9280
 9281    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9282        .unwrap();
 9283    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9284
 9285    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9286    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9287
 9288    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9289    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9290
 9291    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9292        .unwrap();
 9293    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 9294
 9295    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9296        .unwrap();
 9297    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 9298
 9299    // Test case sensitivity
 9300    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 9301    cx.update_editor(|e, window, cx| {
 9302        e.select_previous(&SelectPrevious::default(), window, cx)
 9303            .unwrap();
 9304        e.select_previous(&SelectPrevious::default(), window, cx)
 9305            .unwrap();
 9306    });
 9307    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9308
 9309    // Disable case sensitive search.
 9310    update_test_editor_settings(&mut cx, |settings| {
 9311        let mut search_settings = SearchSettingsContent::default();
 9312        search_settings.case_sensitive = Some(false);
 9313        settings.search = Some(search_settings);
 9314    });
 9315
 9316    cx.set_state("foo\nFOO\n«ˇFoo»");
 9317    cx.update_editor(|e, window, cx| {
 9318        e.select_previous(&SelectPrevious::default(), window, cx)
 9319            .unwrap();
 9320        e.select_previous(&SelectPrevious::default(), window, cx)
 9321            .unwrap();
 9322    });
 9323    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9324}
 9325
 9326#[gpui::test]
 9327async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 9328    init_test(cx, |_| {});
 9329
 9330    let language = Arc::new(Language::new(
 9331        LanguageConfig::default(),
 9332        Some(tree_sitter_rust::LANGUAGE.into()),
 9333    ));
 9334
 9335    let text = r#"
 9336        use mod1::mod2::{mod3, mod4};
 9337
 9338        fn fn_1(param1: bool, param2: &str) {
 9339            let var1 = "text";
 9340        }
 9341    "#
 9342    .unindent();
 9343
 9344    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9345    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9346    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9347
 9348    editor
 9349        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9350        .await;
 9351
 9352    editor.update_in(cx, |editor, window, cx| {
 9353        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9354            s.select_display_ranges([
 9355                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 9356                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 9357                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 9358            ]);
 9359        });
 9360        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9361    });
 9362    editor.update(cx, |editor, cx| {
 9363        assert_text_with_selections(
 9364            editor,
 9365            indoc! {r#"
 9366                use mod1::mod2::{mod3, «mod4ˇ»};
 9367
 9368                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9369                    let var1 = "«ˇtext»";
 9370                }
 9371            "#},
 9372            cx,
 9373        );
 9374    });
 9375
 9376    editor.update_in(cx, |editor, window, cx| {
 9377        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9378    });
 9379    editor.update(cx, |editor, cx| {
 9380        assert_text_with_selections(
 9381            editor,
 9382            indoc! {r#"
 9383                use mod1::mod2::«{mod3, mod4}ˇ»;
 9384
 9385                «ˇfn fn_1(param1: bool, param2: &str) {
 9386                    let var1 = "text";
 9387 9388            "#},
 9389            cx,
 9390        );
 9391    });
 9392
 9393    editor.update_in(cx, |editor, window, cx| {
 9394        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9395    });
 9396    assert_eq!(
 9397        editor.update(cx, |editor, cx| editor
 9398            .selections
 9399            .display_ranges(&editor.display_snapshot(cx))),
 9400        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9401    );
 9402
 9403    // Trying to expand the selected syntax node one more time has no effect.
 9404    editor.update_in(cx, |editor, window, cx| {
 9405        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9406    });
 9407    assert_eq!(
 9408        editor.update(cx, |editor, cx| editor
 9409            .selections
 9410            .display_ranges(&editor.display_snapshot(cx))),
 9411        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9412    );
 9413
 9414    editor.update_in(cx, |editor, window, cx| {
 9415        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9416    });
 9417    editor.update(cx, |editor, cx| {
 9418        assert_text_with_selections(
 9419            editor,
 9420            indoc! {r#"
 9421                use mod1::mod2::«{mod3, mod4}ˇ»;
 9422
 9423                «ˇfn fn_1(param1: bool, param2: &str) {
 9424                    let var1 = "text";
 9425 9426            "#},
 9427            cx,
 9428        );
 9429    });
 9430
 9431    editor.update_in(cx, |editor, window, cx| {
 9432        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9433    });
 9434    editor.update(cx, |editor, cx| {
 9435        assert_text_with_selections(
 9436            editor,
 9437            indoc! {r#"
 9438                use mod1::mod2::{mod3, «mod4ˇ»};
 9439
 9440                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9441                    let var1 = "«ˇtext»";
 9442                }
 9443            "#},
 9444            cx,
 9445        );
 9446    });
 9447
 9448    editor.update_in(cx, |editor, window, cx| {
 9449        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9450    });
 9451    editor.update(cx, |editor, cx| {
 9452        assert_text_with_selections(
 9453            editor,
 9454            indoc! {r#"
 9455                use mod1::mod2::{mod3, moˇd4};
 9456
 9457                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9458                    let var1 = "teˇxt";
 9459                }
 9460            "#},
 9461            cx,
 9462        );
 9463    });
 9464
 9465    // Trying to shrink the selected syntax node one more time has no effect.
 9466    editor.update_in(cx, |editor, window, cx| {
 9467        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9468    });
 9469    editor.update_in(cx, |editor, _, cx| {
 9470        assert_text_with_selections(
 9471            editor,
 9472            indoc! {r#"
 9473                use mod1::mod2::{mod3, moˇd4};
 9474
 9475                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9476                    let var1 = "teˇxt";
 9477                }
 9478            "#},
 9479            cx,
 9480        );
 9481    });
 9482
 9483    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9484    // a fold.
 9485    editor.update_in(cx, |editor, window, cx| {
 9486        editor.fold_creases(
 9487            vec![
 9488                Crease::simple(
 9489                    Point::new(0, 21)..Point::new(0, 24),
 9490                    FoldPlaceholder::test(),
 9491                ),
 9492                Crease::simple(
 9493                    Point::new(3, 20)..Point::new(3, 22),
 9494                    FoldPlaceholder::test(),
 9495                ),
 9496            ],
 9497            true,
 9498            window,
 9499            cx,
 9500        );
 9501        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9502    });
 9503    editor.update(cx, |editor, cx| {
 9504        assert_text_with_selections(
 9505            editor,
 9506            indoc! {r#"
 9507                use mod1::mod2::«{mod3, mod4}ˇ»;
 9508
 9509                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9510                    let var1 = "«ˇtext»";
 9511                }
 9512            "#},
 9513            cx,
 9514        );
 9515    });
 9516}
 9517
 9518#[gpui::test]
 9519async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9520    init_test(cx, |_| {});
 9521
 9522    let language = Arc::new(Language::new(
 9523        LanguageConfig::default(),
 9524        Some(tree_sitter_rust::LANGUAGE.into()),
 9525    ));
 9526
 9527    let text = "let a = 2;";
 9528
 9529    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9530    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9531    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9532
 9533    editor
 9534        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9535        .await;
 9536
 9537    // Test case 1: Cursor at end of word
 9538    editor.update_in(cx, |editor, window, cx| {
 9539        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9540            s.select_display_ranges([
 9541                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9542            ]);
 9543        });
 9544    });
 9545    editor.update(cx, |editor, cx| {
 9546        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9547    });
 9548    editor.update_in(cx, |editor, window, cx| {
 9549        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9550    });
 9551    editor.update(cx, |editor, cx| {
 9552        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9553    });
 9554    editor.update_in(cx, |editor, window, cx| {
 9555        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9556    });
 9557    editor.update(cx, |editor, cx| {
 9558        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9559    });
 9560
 9561    // Test case 2: Cursor at end of statement
 9562    editor.update_in(cx, |editor, window, cx| {
 9563        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9564            s.select_display_ranges([
 9565                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9566            ]);
 9567        });
 9568    });
 9569    editor.update(cx, |editor, cx| {
 9570        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9571    });
 9572    editor.update_in(cx, |editor, window, cx| {
 9573        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9574    });
 9575    editor.update(cx, |editor, cx| {
 9576        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9577    });
 9578}
 9579
 9580#[gpui::test]
 9581async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9582    init_test(cx, |_| {});
 9583
 9584    let language = Arc::new(Language::new(
 9585        LanguageConfig {
 9586            name: "JavaScript".into(),
 9587            ..Default::default()
 9588        },
 9589        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9590    ));
 9591
 9592    let text = r#"
 9593        let a = {
 9594            key: "value",
 9595        };
 9596    "#
 9597    .unindent();
 9598
 9599    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9600    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9601    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9602
 9603    editor
 9604        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9605        .await;
 9606
 9607    // Test case 1: Cursor after '{'
 9608    editor.update_in(cx, |editor, window, cx| {
 9609        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9610            s.select_display_ranges([
 9611                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9612            ]);
 9613        });
 9614    });
 9615    editor.update(cx, |editor, cx| {
 9616        assert_text_with_selections(
 9617            editor,
 9618            indoc! {r#"
 9619                let a = {ˇ
 9620                    key: "value",
 9621                };
 9622            "#},
 9623            cx,
 9624        );
 9625    });
 9626    editor.update_in(cx, |editor, window, cx| {
 9627        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9628    });
 9629    editor.update(cx, |editor, cx| {
 9630        assert_text_with_selections(
 9631            editor,
 9632            indoc! {r#"
 9633                let a = «ˇ{
 9634                    key: "value",
 9635                }»;
 9636            "#},
 9637            cx,
 9638        );
 9639    });
 9640
 9641    // Test case 2: Cursor after ':'
 9642    editor.update_in(cx, |editor, window, cx| {
 9643        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9644            s.select_display_ranges([
 9645                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9646            ]);
 9647        });
 9648    });
 9649    editor.update(cx, |editor, cx| {
 9650        assert_text_with_selections(
 9651            editor,
 9652            indoc! {r#"
 9653                let a = {
 9654                    key:ˇ "value",
 9655                };
 9656            "#},
 9657            cx,
 9658        );
 9659    });
 9660    editor.update_in(cx, |editor, window, cx| {
 9661        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9662    });
 9663    editor.update(cx, |editor, cx| {
 9664        assert_text_with_selections(
 9665            editor,
 9666            indoc! {r#"
 9667                let a = {
 9668                    «ˇkey: "value"»,
 9669                };
 9670            "#},
 9671            cx,
 9672        );
 9673    });
 9674    editor.update_in(cx, |editor, window, cx| {
 9675        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9676    });
 9677    editor.update(cx, |editor, cx| {
 9678        assert_text_with_selections(
 9679            editor,
 9680            indoc! {r#"
 9681                let a = «ˇ{
 9682                    key: "value",
 9683                }»;
 9684            "#},
 9685            cx,
 9686        );
 9687    });
 9688
 9689    // Test case 3: Cursor after ','
 9690    editor.update_in(cx, |editor, window, cx| {
 9691        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9692            s.select_display_ranges([
 9693                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9694            ]);
 9695        });
 9696    });
 9697    editor.update(cx, |editor, cx| {
 9698        assert_text_with_selections(
 9699            editor,
 9700            indoc! {r#"
 9701                let a = {
 9702                    key: "value",ˇ
 9703                };
 9704            "#},
 9705            cx,
 9706        );
 9707    });
 9708    editor.update_in(cx, |editor, window, cx| {
 9709        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9710    });
 9711    editor.update(cx, |editor, cx| {
 9712        assert_text_with_selections(
 9713            editor,
 9714            indoc! {r#"
 9715                let a = «ˇ{
 9716                    key: "value",
 9717                }»;
 9718            "#},
 9719            cx,
 9720        );
 9721    });
 9722
 9723    // Test case 4: Cursor after ';'
 9724    editor.update_in(cx, |editor, window, cx| {
 9725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9726            s.select_display_ranges([
 9727                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9728            ]);
 9729        });
 9730    });
 9731    editor.update(cx, |editor, cx| {
 9732        assert_text_with_selections(
 9733            editor,
 9734            indoc! {r#"
 9735                let a = {
 9736                    key: "value",
 9737                };ˇ
 9738            "#},
 9739            cx,
 9740        );
 9741    });
 9742    editor.update_in(cx, |editor, window, cx| {
 9743        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9744    });
 9745    editor.update(cx, |editor, cx| {
 9746        assert_text_with_selections(
 9747            editor,
 9748            indoc! {r#"
 9749                «ˇlet a = {
 9750                    key: "value",
 9751                };
 9752                »"#},
 9753            cx,
 9754        );
 9755    });
 9756}
 9757
 9758#[gpui::test]
 9759async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9760    init_test(cx, |_| {});
 9761
 9762    let language = Arc::new(Language::new(
 9763        LanguageConfig::default(),
 9764        Some(tree_sitter_rust::LANGUAGE.into()),
 9765    ));
 9766
 9767    let text = r#"
 9768        use mod1::mod2::{mod3, mod4};
 9769
 9770        fn fn_1(param1: bool, param2: &str) {
 9771            let var1 = "hello world";
 9772        }
 9773    "#
 9774    .unindent();
 9775
 9776    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9777    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9778    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9779
 9780    editor
 9781        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9782        .await;
 9783
 9784    // Test 1: Cursor on a letter of a string word
 9785    editor.update_in(cx, |editor, window, cx| {
 9786        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9787            s.select_display_ranges([
 9788                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9789            ]);
 9790        });
 9791    });
 9792    editor.update_in(cx, |editor, window, cx| {
 9793        assert_text_with_selections(
 9794            editor,
 9795            indoc! {r#"
 9796                use mod1::mod2::{mod3, mod4};
 9797
 9798                fn fn_1(param1: bool, param2: &str) {
 9799                    let var1 = "hˇello world";
 9800                }
 9801            "#},
 9802            cx,
 9803        );
 9804        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9805        assert_text_with_selections(
 9806            editor,
 9807            indoc! {r#"
 9808                use mod1::mod2::{mod3, mod4};
 9809
 9810                fn fn_1(param1: bool, param2: &str) {
 9811                    let var1 = "«ˇhello» world";
 9812                }
 9813            "#},
 9814            cx,
 9815        );
 9816    });
 9817
 9818    // Test 2: Partial selection within a word
 9819    editor.update_in(cx, |editor, window, cx| {
 9820        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9821            s.select_display_ranges([
 9822                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9823            ]);
 9824        });
 9825    });
 9826    editor.update_in(cx, |editor, window, cx| {
 9827        assert_text_with_selections(
 9828            editor,
 9829            indoc! {r#"
 9830                use mod1::mod2::{mod3, mod4};
 9831
 9832                fn fn_1(param1: bool, param2: &str) {
 9833                    let var1 = "h«elˇ»lo world";
 9834                }
 9835            "#},
 9836            cx,
 9837        );
 9838        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9839        assert_text_with_selections(
 9840            editor,
 9841            indoc! {r#"
 9842                use mod1::mod2::{mod3, mod4};
 9843
 9844                fn fn_1(param1: bool, param2: &str) {
 9845                    let var1 = "«ˇhello» world";
 9846                }
 9847            "#},
 9848            cx,
 9849        );
 9850    });
 9851
 9852    // Test 3: Complete word already selected
 9853    editor.update_in(cx, |editor, window, cx| {
 9854        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9855            s.select_display_ranges([
 9856                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9857            ]);
 9858        });
 9859    });
 9860    editor.update_in(cx, |editor, window, cx| {
 9861        assert_text_with_selections(
 9862            editor,
 9863            indoc! {r#"
 9864                use mod1::mod2::{mod3, mod4};
 9865
 9866                fn fn_1(param1: bool, param2: &str) {
 9867                    let var1 = "«helloˇ» world";
 9868                }
 9869            "#},
 9870            cx,
 9871        );
 9872        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9873        assert_text_with_selections(
 9874            editor,
 9875            indoc! {r#"
 9876                use mod1::mod2::{mod3, mod4};
 9877
 9878                fn fn_1(param1: bool, param2: &str) {
 9879                    let var1 = "«hello worldˇ»";
 9880                }
 9881            "#},
 9882            cx,
 9883        );
 9884    });
 9885
 9886    // Test 4: Selection spanning across words
 9887    editor.update_in(cx, |editor, window, cx| {
 9888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9889            s.select_display_ranges([
 9890                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9891            ]);
 9892        });
 9893    });
 9894    editor.update_in(cx, |editor, window, cx| {
 9895        assert_text_with_selections(
 9896            editor,
 9897            indoc! {r#"
 9898                use mod1::mod2::{mod3, mod4};
 9899
 9900                fn fn_1(param1: bool, param2: &str) {
 9901                    let var1 = "hel«lo woˇ»rld";
 9902                }
 9903            "#},
 9904            cx,
 9905        );
 9906        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9907        assert_text_with_selections(
 9908            editor,
 9909            indoc! {r#"
 9910                use mod1::mod2::{mod3, mod4};
 9911
 9912                fn fn_1(param1: bool, param2: &str) {
 9913                    let var1 = "«ˇhello world»";
 9914                }
 9915            "#},
 9916            cx,
 9917        );
 9918    });
 9919
 9920    // Test 5: Expansion beyond string
 9921    editor.update_in(cx, |editor, window, cx| {
 9922        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9923        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9924        assert_text_with_selections(
 9925            editor,
 9926            indoc! {r#"
 9927                use mod1::mod2::{mod3, mod4};
 9928
 9929                fn fn_1(param1: bool, param2: &str) {
 9930                    «ˇlet var1 = "hello world";»
 9931                }
 9932            "#},
 9933            cx,
 9934        );
 9935    });
 9936}
 9937
 9938#[gpui::test]
 9939async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9940    init_test(cx, |_| {});
 9941
 9942    let mut cx = EditorTestContext::new(cx).await;
 9943
 9944    let language = Arc::new(Language::new(
 9945        LanguageConfig::default(),
 9946        Some(tree_sitter_rust::LANGUAGE.into()),
 9947    ));
 9948
 9949    cx.update_buffer(|buffer, cx| {
 9950        buffer.set_language(Some(language), cx);
 9951    });
 9952
 9953    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9954    cx.update_editor(|editor, window, cx| {
 9955        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9956    });
 9957
 9958    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9959
 9960    cx.set_state(indoc! { r#"fn a() {
 9961          // what
 9962          // a
 9963          // ˇlong
 9964          // method
 9965          // I
 9966          // sure
 9967          // hope
 9968          // it
 9969          // works
 9970    }"# });
 9971
 9972    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9973    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9974    cx.update(|_, cx| {
 9975        multi_buffer.update(cx, |multi_buffer, cx| {
 9976            multi_buffer.set_excerpts_for_path(
 9977                PathKey::for_buffer(&buffer, cx),
 9978                buffer,
 9979                [Point::new(1, 0)..Point::new(1, 0)],
 9980                3,
 9981                cx,
 9982            );
 9983        });
 9984    });
 9985
 9986    let editor2 = cx.new_window_entity(|window, cx| {
 9987        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9988    });
 9989
 9990    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9991    cx.update_editor(|editor, window, cx| {
 9992        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9993            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9994        })
 9995    });
 9996
 9997    cx.assert_editor_state(indoc! { "
 9998        fn a() {
 9999              // what
10000              // a
10001        ˇ      // long
10002              // method"});
10003
10004    cx.update_editor(|editor, window, cx| {
10005        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
10006    });
10007
10008    // Although we could potentially make the action work when the syntax node
10009    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
10010    // did. Maybe we could also expand the excerpt to contain the range?
10011    cx.assert_editor_state(indoc! { "
10012        fn a() {
10013              // what
10014              // a
10015        ˇ      // long
10016              // method"});
10017}
10018
10019#[gpui::test]
10020async fn test_fold_function_bodies(cx: &mut TestAppContext) {
10021    init_test(cx, |_| {});
10022
10023    let base_text = r#"
10024        impl A {
10025            // this is an uncommitted comment
10026
10027            fn b() {
10028                c();
10029            }
10030
10031            // this is another uncommitted comment
10032
10033            fn d() {
10034                // e
10035                // f
10036            }
10037        }
10038
10039        fn g() {
10040            // h
10041        }
10042    "#
10043    .unindent();
10044
10045    let text = r#"
10046        ˇimpl A {
10047
10048            fn b() {
10049                c();
10050            }
10051
10052            fn d() {
10053                // e
10054                // f
10055            }
10056        }
10057
10058        fn g() {
10059            // h
10060        }
10061    "#
10062    .unindent();
10063
10064    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10065    cx.set_state(&text);
10066    cx.set_head_text(&base_text);
10067    cx.update_editor(|editor, window, cx| {
10068        editor.expand_all_diff_hunks(&Default::default(), window, cx);
10069    });
10070
10071    cx.assert_state_with_diff(
10072        "
10073        ˇimpl A {
10074      -     // this is an uncommitted comment
10075
10076            fn b() {
10077                c();
10078            }
10079
10080      -     // this is another uncommitted comment
10081      -
10082            fn d() {
10083                // e
10084                // f
10085            }
10086        }
10087
10088        fn g() {
10089            // h
10090        }
10091    "
10092        .unindent(),
10093    );
10094
10095    let expected_display_text = "
10096        impl A {
10097            // this is an uncommitted comment
10098
10099            fn b() {
1010010101            }
10102
10103            // this is another uncommitted comment
10104
10105            fn d() {
1010610107            }
10108        }
10109
10110        fn g() {
1011110112        }
10113        "
10114    .unindent();
10115
10116    cx.update_editor(|editor, window, cx| {
10117        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
10118        assert_eq!(editor.display_text(cx), expected_display_text);
10119    });
10120}
10121
10122#[gpui::test]
10123async fn test_autoindent(cx: &mut TestAppContext) {
10124    init_test(cx, |_| {});
10125
10126    let language = Arc::new(
10127        Language::new(
10128            LanguageConfig {
10129                brackets: BracketPairConfig {
10130                    pairs: vec![
10131                        BracketPair {
10132                            start: "{".to_string(),
10133                            end: "}".to_string(),
10134                            close: false,
10135                            surround: false,
10136                            newline: true,
10137                        },
10138                        BracketPair {
10139                            start: "(".to_string(),
10140                            end: ")".to_string(),
10141                            close: false,
10142                            surround: false,
10143                            newline: true,
10144                        },
10145                    ],
10146                    ..Default::default()
10147                },
10148                ..Default::default()
10149            },
10150            Some(tree_sitter_rust::LANGUAGE.into()),
10151        )
10152        .with_indents_query(
10153            r#"
10154                (_ "(" ")" @end) @indent
10155                (_ "{" "}" @end) @indent
10156            "#,
10157        )
10158        .unwrap(),
10159    );
10160
10161    let text = "fn a() {}";
10162
10163    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10164    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10165    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10166    editor
10167        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10168        .await;
10169
10170    editor.update_in(cx, |editor, window, cx| {
10171        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10172            s.select_ranges([
10173                MultiBufferOffset(5)..MultiBufferOffset(5),
10174                MultiBufferOffset(8)..MultiBufferOffset(8),
10175                MultiBufferOffset(9)..MultiBufferOffset(9),
10176            ])
10177        });
10178        editor.newline(&Newline, window, cx);
10179        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
10180        assert_eq!(
10181            editor.selections.ranges(&editor.display_snapshot(cx)),
10182            &[
10183                Point::new(1, 4)..Point::new(1, 4),
10184                Point::new(3, 4)..Point::new(3, 4),
10185                Point::new(5, 0)..Point::new(5, 0)
10186            ]
10187        );
10188    });
10189}
10190
10191#[gpui::test]
10192async fn test_autoindent_disabled(cx: &mut TestAppContext) {
10193    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
10194
10195    let language = Arc::new(
10196        Language::new(
10197            LanguageConfig {
10198                brackets: BracketPairConfig {
10199                    pairs: vec![
10200                        BracketPair {
10201                            start: "{".to_string(),
10202                            end: "}".to_string(),
10203                            close: false,
10204                            surround: false,
10205                            newline: true,
10206                        },
10207                        BracketPair {
10208                            start: "(".to_string(),
10209                            end: ")".to_string(),
10210                            close: false,
10211                            surround: false,
10212                            newline: true,
10213                        },
10214                    ],
10215                    ..Default::default()
10216                },
10217                ..Default::default()
10218            },
10219            Some(tree_sitter_rust::LANGUAGE.into()),
10220        )
10221        .with_indents_query(
10222            r#"
10223                (_ "(" ")" @end) @indent
10224                (_ "{" "}" @end) @indent
10225            "#,
10226        )
10227        .unwrap(),
10228    );
10229
10230    let text = "fn a() {}";
10231
10232    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10233    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10234    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10235    editor
10236        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10237        .await;
10238
10239    editor.update_in(cx, |editor, window, cx| {
10240        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10241            s.select_ranges([
10242                MultiBufferOffset(5)..MultiBufferOffset(5),
10243                MultiBufferOffset(8)..MultiBufferOffset(8),
10244                MultiBufferOffset(9)..MultiBufferOffset(9),
10245            ])
10246        });
10247        editor.newline(&Newline, window, cx);
10248        assert_eq!(
10249            editor.text(cx),
10250            indoc!(
10251                "
10252                fn a(
10253
10254                ) {
10255
10256                }
10257                "
10258            )
10259        );
10260        assert_eq!(
10261            editor.selections.ranges(&editor.display_snapshot(cx)),
10262            &[
10263                Point::new(1, 0)..Point::new(1, 0),
10264                Point::new(3, 0)..Point::new(3, 0),
10265                Point::new(5, 0)..Point::new(5, 0)
10266            ]
10267        );
10268    });
10269}
10270
10271#[gpui::test]
10272async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
10273    init_test(cx, |settings| {
10274        settings.defaults.auto_indent = Some(true);
10275        settings.languages.0.insert(
10276            "python".into(),
10277            LanguageSettingsContent {
10278                auto_indent: Some(false),
10279                ..Default::default()
10280            },
10281        );
10282    });
10283
10284    let mut cx = EditorTestContext::new(cx).await;
10285
10286    let injected_language = Arc::new(
10287        Language::new(
10288            LanguageConfig {
10289                brackets: BracketPairConfig {
10290                    pairs: vec![
10291                        BracketPair {
10292                            start: "{".to_string(),
10293                            end: "}".to_string(),
10294                            close: false,
10295                            surround: false,
10296                            newline: true,
10297                        },
10298                        BracketPair {
10299                            start: "(".to_string(),
10300                            end: ")".to_string(),
10301                            close: true,
10302                            surround: false,
10303                            newline: true,
10304                        },
10305                    ],
10306                    ..Default::default()
10307                },
10308                name: "python".into(),
10309                ..Default::default()
10310            },
10311            Some(tree_sitter_python::LANGUAGE.into()),
10312        )
10313        .with_indents_query(
10314            r#"
10315                (_ "(" ")" @end) @indent
10316                (_ "{" "}" @end) @indent
10317            "#,
10318        )
10319        .unwrap(),
10320    );
10321
10322    let language = Arc::new(
10323        Language::new(
10324            LanguageConfig {
10325                brackets: BracketPairConfig {
10326                    pairs: vec![
10327                        BracketPair {
10328                            start: "{".to_string(),
10329                            end: "}".to_string(),
10330                            close: false,
10331                            surround: false,
10332                            newline: true,
10333                        },
10334                        BracketPair {
10335                            start: "(".to_string(),
10336                            end: ")".to_string(),
10337                            close: true,
10338                            surround: false,
10339                            newline: true,
10340                        },
10341                    ],
10342                    ..Default::default()
10343                },
10344                name: LanguageName::new_static("rust"),
10345                ..Default::default()
10346            },
10347            Some(tree_sitter_rust::LANGUAGE.into()),
10348        )
10349        .with_indents_query(
10350            r#"
10351                (_ "(" ")" @end) @indent
10352                (_ "{" "}" @end) @indent
10353            "#,
10354        )
10355        .unwrap()
10356        .with_injection_query(
10357            r#"
10358            (macro_invocation
10359                macro: (identifier) @_macro_name
10360                (token_tree) @injection.content
10361                (#set! injection.language "python"))
10362           "#,
10363        )
10364        .unwrap(),
10365    );
10366
10367    cx.language_registry().add(injected_language);
10368    cx.language_registry().add(language.clone());
10369
10370    cx.update_buffer(|buffer, cx| {
10371        buffer.set_language(Some(language), cx);
10372    });
10373
10374    cx.set_state(r#"struct A {ˇ}"#);
10375
10376    cx.update_editor(|editor, window, cx| {
10377        editor.newline(&Default::default(), window, cx);
10378    });
10379
10380    cx.assert_editor_state(indoc!(
10381        "struct A {
10382            ˇ
10383        }"
10384    ));
10385
10386    cx.set_state(r#"select_biased!(ˇ)"#);
10387
10388    cx.update_editor(|editor, window, cx| {
10389        editor.newline(&Default::default(), window, cx);
10390        editor.handle_input("def ", window, cx);
10391        editor.handle_input("(", window, cx);
10392        editor.newline(&Default::default(), window, cx);
10393        editor.handle_input("a", window, cx);
10394    });
10395
10396    cx.assert_editor_state(indoc!(
10397        "select_biased!(
10398        def (
1039910400        )
10401        )"
10402    ));
10403}
10404
10405#[gpui::test]
10406async fn test_autoindent_selections(cx: &mut TestAppContext) {
10407    init_test(cx, |_| {});
10408
10409    {
10410        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10411        cx.set_state(indoc! {"
10412            impl A {
10413
10414                fn b() {}
10415
10416            «fn c() {
10417
10418            }ˇ»
10419            }
10420        "});
10421
10422        cx.update_editor(|editor, window, cx| {
10423            editor.autoindent(&Default::default(), window, cx);
10424        });
10425        cx.wait_for_autoindent_applied().await;
10426
10427        cx.assert_editor_state(indoc! {"
10428            impl A {
10429
10430                fn b() {}
10431
10432                «fn c() {
10433
10434                }ˇ»
10435            }
10436        "});
10437    }
10438
10439    {
10440        let mut cx = EditorTestContext::new_multibuffer(
10441            cx,
10442            [indoc! { "
10443                impl A {
10444                «
10445                // a
10446                fn b(){}
10447                »
10448                «
10449                    }
10450                    fn c(){}
10451                »
10452            "}],
10453        );
10454
10455        let buffer = cx.update_editor(|editor, _, cx| {
10456            let buffer = editor.buffer().update(cx, |buffer, _| {
10457                buffer.all_buffers().iter().next().unwrap().clone()
10458            });
10459            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10460            buffer
10461        });
10462
10463        cx.run_until_parked();
10464        cx.update_editor(|editor, window, cx| {
10465            editor.select_all(&Default::default(), window, cx);
10466            editor.autoindent(&Default::default(), window, cx)
10467        });
10468        cx.run_until_parked();
10469
10470        cx.update(|_, cx| {
10471            assert_eq!(
10472                buffer.read(cx).text(),
10473                indoc! { "
10474                    impl A {
10475
10476                        // a
10477                        fn b(){}
10478
10479
10480                    }
10481                    fn c(){}
10482
10483                " }
10484            )
10485        });
10486    }
10487}
10488
10489#[gpui::test]
10490async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10491    init_test(cx, |_| {});
10492
10493    let mut cx = EditorTestContext::new(cx).await;
10494
10495    let language = Arc::new(Language::new(
10496        LanguageConfig {
10497            brackets: BracketPairConfig {
10498                pairs: vec![
10499                    BracketPair {
10500                        start: "{".to_string(),
10501                        end: "}".to_string(),
10502                        close: true,
10503                        surround: true,
10504                        newline: true,
10505                    },
10506                    BracketPair {
10507                        start: "(".to_string(),
10508                        end: ")".to_string(),
10509                        close: true,
10510                        surround: true,
10511                        newline: true,
10512                    },
10513                    BracketPair {
10514                        start: "/*".to_string(),
10515                        end: " */".to_string(),
10516                        close: true,
10517                        surround: true,
10518                        newline: true,
10519                    },
10520                    BracketPair {
10521                        start: "[".to_string(),
10522                        end: "]".to_string(),
10523                        close: false,
10524                        surround: false,
10525                        newline: true,
10526                    },
10527                    BracketPair {
10528                        start: "\"".to_string(),
10529                        end: "\"".to_string(),
10530                        close: true,
10531                        surround: true,
10532                        newline: false,
10533                    },
10534                    BracketPair {
10535                        start: "<".to_string(),
10536                        end: ">".to_string(),
10537                        close: false,
10538                        surround: true,
10539                        newline: true,
10540                    },
10541                ],
10542                ..Default::default()
10543            },
10544            autoclose_before: "})]".to_string(),
10545            ..Default::default()
10546        },
10547        Some(tree_sitter_rust::LANGUAGE.into()),
10548    ));
10549
10550    cx.language_registry().add(language.clone());
10551    cx.update_buffer(|buffer, cx| {
10552        buffer.set_language(Some(language), cx);
10553    });
10554
10555    cx.set_state(
10556        &r#"
10557            🏀ˇ
10558            εˇ
10559            ❤️ˇ
10560        "#
10561        .unindent(),
10562    );
10563
10564    // autoclose multiple nested brackets at multiple cursors
10565    cx.update_editor(|editor, window, cx| {
10566        editor.handle_input("{", window, cx);
10567        editor.handle_input("{", window, cx);
10568        editor.handle_input("{", window, cx);
10569    });
10570    cx.assert_editor_state(
10571        &"
10572            🏀{{{ˇ}}}
10573            ε{{{ˇ}}}
10574            ❤️{{{ˇ}}}
10575        "
10576        .unindent(),
10577    );
10578
10579    // insert a different closing bracket
10580    cx.update_editor(|editor, window, cx| {
10581        editor.handle_input(")", window, cx);
10582    });
10583    cx.assert_editor_state(
10584        &"
10585            🏀{{{)ˇ}}}
10586            ε{{{)ˇ}}}
10587            ❤️{{{)ˇ}}}
10588        "
10589        .unindent(),
10590    );
10591
10592    // skip over the auto-closed brackets when typing a closing bracket
10593    cx.update_editor(|editor, window, cx| {
10594        editor.move_right(&MoveRight, window, cx);
10595        editor.handle_input("}", window, cx);
10596        editor.handle_input("}", window, cx);
10597        editor.handle_input("}", window, cx);
10598    });
10599    cx.assert_editor_state(
10600        &"
10601            🏀{{{)}}}}ˇ
10602            ε{{{)}}}}ˇ
10603            ❤️{{{)}}}}ˇ
10604        "
10605        .unindent(),
10606    );
10607
10608    // autoclose multi-character pairs
10609    cx.set_state(
10610        &"
10611            ˇ
10612            ˇ
10613        "
10614        .unindent(),
10615    );
10616    cx.update_editor(|editor, window, cx| {
10617        editor.handle_input("/", window, cx);
10618        editor.handle_input("*", window, cx);
10619    });
10620    cx.assert_editor_state(
10621        &"
10622            /*ˇ */
10623            /*ˇ */
10624        "
10625        .unindent(),
10626    );
10627
10628    // one cursor autocloses a multi-character pair, one cursor
10629    // does not autoclose.
10630    cx.set_state(
10631        &"
1063210633            ˇ
10634        "
10635        .unindent(),
10636    );
10637    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10638    cx.assert_editor_state(
10639        &"
10640            /*ˇ */
1064110642        "
10643        .unindent(),
10644    );
10645
10646    // Don't autoclose if the next character isn't whitespace and isn't
10647    // listed in the language's "autoclose_before" section.
10648    cx.set_state("ˇa b");
10649    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10650    cx.assert_editor_state("{ˇa b");
10651
10652    // Don't autoclose if `close` is false for the bracket pair
10653    cx.set_state("ˇ");
10654    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10655    cx.assert_editor_state("");
10656
10657    // Surround with brackets if text is selected
10658    cx.set_state("«aˇ» b");
10659    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10660    cx.assert_editor_state("{«aˇ»} b");
10661
10662    // Autoclose when not immediately after a word character
10663    cx.set_state("a ˇ");
10664    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10665    cx.assert_editor_state("a \"ˇ\"");
10666
10667    // Autoclose pair where the start and end characters are the same
10668    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10669    cx.assert_editor_state("a \"\"ˇ");
10670
10671    // Don't autoclose when immediately after a word character
10672    cx.set_state("");
10673    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10674    cx.assert_editor_state("a\"ˇ");
10675
10676    // Do autoclose when after a non-word character
10677    cx.set_state("");
10678    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10679    cx.assert_editor_state("{\"ˇ\"");
10680
10681    // Non identical pairs autoclose regardless of preceding character
10682    cx.set_state("");
10683    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10684    cx.assert_editor_state("a{ˇ}");
10685
10686    // Don't autoclose pair if autoclose is disabled
10687    cx.set_state("ˇ");
10688    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10689    cx.assert_editor_state("");
10690
10691    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10692    cx.set_state("«aˇ» b");
10693    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10694    cx.assert_editor_state("<«aˇ»> b");
10695}
10696
10697#[gpui::test]
10698async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10699    init_test(cx, |settings| {
10700        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10701    });
10702
10703    let mut cx = EditorTestContext::new(cx).await;
10704
10705    let language = Arc::new(Language::new(
10706        LanguageConfig {
10707            brackets: BracketPairConfig {
10708                pairs: vec![
10709                    BracketPair {
10710                        start: "{".to_string(),
10711                        end: "}".to_string(),
10712                        close: true,
10713                        surround: true,
10714                        newline: true,
10715                    },
10716                    BracketPair {
10717                        start: "(".to_string(),
10718                        end: ")".to_string(),
10719                        close: true,
10720                        surround: true,
10721                        newline: true,
10722                    },
10723                    BracketPair {
10724                        start: "[".to_string(),
10725                        end: "]".to_string(),
10726                        close: false,
10727                        surround: false,
10728                        newline: true,
10729                    },
10730                ],
10731                ..Default::default()
10732            },
10733            autoclose_before: "})]".to_string(),
10734            ..Default::default()
10735        },
10736        Some(tree_sitter_rust::LANGUAGE.into()),
10737    ));
10738
10739    cx.language_registry().add(language.clone());
10740    cx.update_buffer(|buffer, cx| {
10741        buffer.set_language(Some(language), cx);
10742    });
10743
10744    cx.set_state(
10745        &"
10746            ˇ
10747            ˇ
10748            ˇ
10749        "
10750        .unindent(),
10751    );
10752
10753    // ensure only matching closing brackets are skipped over
10754    cx.update_editor(|editor, window, cx| {
10755        editor.handle_input("}", window, cx);
10756        editor.move_left(&MoveLeft, window, cx);
10757        editor.handle_input(")", window, cx);
10758        editor.move_left(&MoveLeft, window, cx);
10759    });
10760    cx.assert_editor_state(
10761        &"
10762            ˇ)}
10763            ˇ)}
10764            ˇ)}
10765        "
10766        .unindent(),
10767    );
10768
10769    // skip-over closing brackets at multiple cursors
10770    cx.update_editor(|editor, window, cx| {
10771        editor.handle_input(")", window, cx);
10772        editor.handle_input("}", window, cx);
10773    });
10774    cx.assert_editor_state(
10775        &"
10776            )}ˇ
10777            )}ˇ
10778            )}ˇ
10779        "
10780        .unindent(),
10781    );
10782
10783    // ignore non-close brackets
10784    cx.update_editor(|editor, window, cx| {
10785        editor.handle_input("]", window, cx);
10786        editor.move_left(&MoveLeft, window, cx);
10787        editor.handle_input("]", window, cx);
10788    });
10789    cx.assert_editor_state(
10790        &"
10791            )}]ˇ]
10792            )}]ˇ]
10793            )}]ˇ]
10794        "
10795        .unindent(),
10796    );
10797}
10798
10799#[gpui::test]
10800async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10801    init_test(cx, |_| {});
10802
10803    let mut cx = EditorTestContext::new(cx).await;
10804
10805    let html_language = Arc::new(
10806        Language::new(
10807            LanguageConfig {
10808                name: "HTML".into(),
10809                brackets: BracketPairConfig {
10810                    pairs: vec![
10811                        BracketPair {
10812                            start: "<".into(),
10813                            end: ">".into(),
10814                            close: true,
10815                            ..Default::default()
10816                        },
10817                        BracketPair {
10818                            start: "{".into(),
10819                            end: "}".into(),
10820                            close: true,
10821                            ..Default::default()
10822                        },
10823                        BracketPair {
10824                            start: "(".into(),
10825                            end: ")".into(),
10826                            close: true,
10827                            ..Default::default()
10828                        },
10829                    ],
10830                    ..Default::default()
10831                },
10832                autoclose_before: "})]>".into(),
10833                ..Default::default()
10834            },
10835            Some(tree_sitter_html::LANGUAGE.into()),
10836        )
10837        .with_injection_query(
10838            r#"
10839            (script_element
10840                (raw_text) @injection.content
10841                (#set! injection.language "javascript"))
10842            "#,
10843        )
10844        .unwrap(),
10845    );
10846
10847    let javascript_language = Arc::new(Language::new(
10848        LanguageConfig {
10849            name: "JavaScript".into(),
10850            brackets: BracketPairConfig {
10851                pairs: vec![
10852                    BracketPair {
10853                        start: "/*".into(),
10854                        end: " */".into(),
10855                        close: true,
10856                        ..Default::default()
10857                    },
10858                    BracketPair {
10859                        start: "{".into(),
10860                        end: "}".into(),
10861                        close: true,
10862                        ..Default::default()
10863                    },
10864                    BracketPair {
10865                        start: "(".into(),
10866                        end: ")".into(),
10867                        close: true,
10868                        ..Default::default()
10869                    },
10870                ],
10871                ..Default::default()
10872            },
10873            autoclose_before: "})]>".into(),
10874            ..Default::default()
10875        },
10876        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10877    ));
10878
10879    cx.language_registry().add(html_language.clone());
10880    cx.language_registry().add(javascript_language);
10881    cx.executor().run_until_parked();
10882
10883    cx.update_buffer(|buffer, cx| {
10884        buffer.set_language(Some(html_language), cx);
10885    });
10886
10887    cx.set_state(
10888        &r#"
10889            <body>ˇ
10890                <script>
10891                    var x = 1;ˇ
10892                </script>
10893            </body>ˇ
10894        "#
10895        .unindent(),
10896    );
10897
10898    // Precondition: different languages are active at different locations.
10899    cx.update_editor(|editor, window, cx| {
10900        let snapshot = editor.snapshot(window, cx);
10901        let cursors = editor
10902            .selections
10903            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10904        let languages = cursors
10905            .iter()
10906            .map(|c| snapshot.language_at(c.start).unwrap().name())
10907            .collect::<Vec<_>>();
10908        assert_eq!(
10909            languages,
10910            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10911        );
10912    });
10913
10914    // Angle brackets autoclose in HTML, but not JavaScript.
10915    cx.update_editor(|editor, window, cx| {
10916        editor.handle_input("<", window, cx);
10917        editor.handle_input("a", window, cx);
10918    });
10919    cx.assert_editor_state(
10920        &r#"
10921            <body><aˇ>
10922                <script>
10923                    var x = 1;<aˇ
10924                </script>
10925            </body><aˇ>
10926        "#
10927        .unindent(),
10928    );
10929
10930    // Curly braces and parens autoclose in both HTML and JavaScript.
10931    cx.update_editor(|editor, window, cx| {
10932        editor.handle_input(" b=", window, cx);
10933        editor.handle_input("{", window, cx);
10934        editor.handle_input("c", window, cx);
10935        editor.handle_input("(", window, cx);
10936    });
10937    cx.assert_editor_state(
10938        &r#"
10939            <body><a b={c(ˇ)}>
10940                <script>
10941                    var x = 1;<a b={c(ˇ)}
10942                </script>
10943            </body><a b={c(ˇ)}>
10944        "#
10945        .unindent(),
10946    );
10947
10948    // Brackets that were already autoclosed are skipped.
10949    cx.update_editor(|editor, window, cx| {
10950        editor.handle_input(")", window, cx);
10951        editor.handle_input("d", window, cx);
10952        editor.handle_input("}", window, cx);
10953    });
10954    cx.assert_editor_state(
10955        &r#"
10956            <body><a b={c()d}ˇ>
10957                <script>
10958                    var x = 1;<a b={c()d}ˇ
10959                </script>
10960            </body><a b={c()d}ˇ>
10961        "#
10962        .unindent(),
10963    );
10964    cx.update_editor(|editor, window, cx| {
10965        editor.handle_input(">", window, cx);
10966    });
10967    cx.assert_editor_state(
10968        &r#"
10969            <body><a b={c()d}>ˇ
10970                <script>
10971                    var x = 1;<a b={c()d}>ˇ
10972                </script>
10973            </body><a b={c()d}>ˇ
10974        "#
10975        .unindent(),
10976    );
10977
10978    // Reset
10979    cx.set_state(
10980        &r#"
10981            <body>ˇ
10982                <script>
10983                    var x = 1;ˇ
10984                </script>
10985            </body>ˇ
10986        "#
10987        .unindent(),
10988    );
10989
10990    cx.update_editor(|editor, window, cx| {
10991        editor.handle_input("<", window, cx);
10992    });
10993    cx.assert_editor_state(
10994        &r#"
10995            <body><ˇ>
10996                <script>
10997                    var x = 1;<ˇ
10998                </script>
10999            </body><ˇ>
11000        "#
11001        .unindent(),
11002    );
11003
11004    // When backspacing, the closing angle brackets are removed.
11005    cx.update_editor(|editor, window, cx| {
11006        editor.backspace(&Backspace, window, cx);
11007    });
11008    cx.assert_editor_state(
11009        &r#"
11010            <body>ˇ
11011                <script>
11012                    var x = 1;ˇ
11013                </script>
11014            </body>ˇ
11015        "#
11016        .unindent(),
11017    );
11018
11019    // Block comments autoclose in JavaScript, but not HTML.
11020    cx.update_editor(|editor, window, cx| {
11021        editor.handle_input("/", window, cx);
11022        editor.handle_input("*", window, cx);
11023    });
11024    cx.assert_editor_state(
11025        &r#"
11026            <body>/*ˇ
11027                <script>
11028                    var x = 1;/*ˇ */
11029                </script>
11030            </body>/*ˇ
11031        "#
11032        .unindent(),
11033    );
11034}
11035
11036#[gpui::test]
11037async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
11038    init_test(cx, |_| {});
11039
11040    let mut cx = EditorTestContext::new(cx).await;
11041
11042    let rust_language = Arc::new(
11043        Language::new(
11044            LanguageConfig {
11045                name: "Rust".into(),
11046                brackets: serde_json::from_value(json!([
11047                    { "start": "{", "end": "}", "close": true, "newline": true },
11048                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
11049                ]))
11050                .unwrap(),
11051                autoclose_before: "})]>".into(),
11052                ..Default::default()
11053            },
11054            Some(tree_sitter_rust::LANGUAGE.into()),
11055        )
11056        .with_override_query("(string_literal) @string")
11057        .unwrap(),
11058    );
11059
11060    cx.language_registry().add(rust_language.clone());
11061    cx.update_buffer(|buffer, cx| {
11062        buffer.set_language(Some(rust_language), cx);
11063    });
11064
11065    cx.set_state(
11066        &r#"
11067            let x = ˇ
11068        "#
11069        .unindent(),
11070    );
11071
11072    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
11073    cx.update_editor(|editor, window, cx| {
11074        editor.handle_input("\"", window, cx);
11075    });
11076    cx.assert_editor_state(
11077        &r#"
11078            let x = "ˇ"
11079        "#
11080        .unindent(),
11081    );
11082
11083    // Inserting another quotation mark. The cursor moves across the existing
11084    // automatically-inserted quotation mark.
11085    cx.update_editor(|editor, window, cx| {
11086        editor.handle_input("\"", window, cx);
11087    });
11088    cx.assert_editor_state(
11089        &r#"
11090            let x = ""ˇ
11091        "#
11092        .unindent(),
11093    );
11094
11095    // Reset
11096    cx.set_state(
11097        &r#"
11098            let x = ˇ
11099        "#
11100        .unindent(),
11101    );
11102
11103    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
11104    cx.update_editor(|editor, window, cx| {
11105        editor.handle_input("\"", window, cx);
11106        editor.handle_input(" ", window, cx);
11107        editor.move_left(&Default::default(), window, cx);
11108        editor.handle_input("\\", window, cx);
11109        editor.handle_input("\"", window, cx);
11110    });
11111    cx.assert_editor_state(
11112        &r#"
11113            let x = "\"ˇ "
11114        "#
11115        .unindent(),
11116    );
11117
11118    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
11119    // mark. Nothing is inserted.
11120    cx.update_editor(|editor, window, cx| {
11121        editor.move_right(&Default::default(), window, cx);
11122        editor.handle_input("\"", window, cx);
11123    });
11124    cx.assert_editor_state(
11125        &r#"
11126            let x = "\" "ˇ
11127        "#
11128        .unindent(),
11129    );
11130}
11131
11132#[gpui::test]
11133async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
11134    init_test(cx, |_| {});
11135
11136    let mut cx = EditorTestContext::new(cx).await;
11137    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
11138
11139    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11140
11141    // Double quote inside single-quoted string
11142    cx.set_state(indoc! {r#"
11143        def main():
11144            items = ['"', ˇ]
11145    "#});
11146    cx.update_editor(|editor, window, cx| {
11147        editor.handle_input("\"", window, cx);
11148    });
11149    cx.assert_editor_state(indoc! {r#"
11150        def main():
11151            items = ['"', "ˇ"]
11152    "#});
11153
11154    // Two double quotes inside single-quoted string
11155    cx.set_state(indoc! {r#"
11156        def main():
11157            items = ['""', ˇ]
11158    "#});
11159    cx.update_editor(|editor, window, cx| {
11160        editor.handle_input("\"", window, cx);
11161    });
11162    cx.assert_editor_state(indoc! {r#"
11163        def main():
11164            items = ['""', "ˇ"]
11165    "#});
11166
11167    // Single quote inside double-quoted string
11168    cx.set_state(indoc! {r#"
11169        def main():
11170            items = ["'", ˇ]
11171    "#});
11172    cx.update_editor(|editor, window, cx| {
11173        editor.handle_input("'", window, cx);
11174    });
11175    cx.assert_editor_state(indoc! {r#"
11176        def main():
11177            items = ["'", 'ˇ']
11178    "#});
11179
11180    // Two single quotes inside double-quoted string
11181    cx.set_state(indoc! {r#"
11182        def main():
11183            items = ["''", ˇ]
11184    "#});
11185    cx.update_editor(|editor, window, cx| {
11186        editor.handle_input("'", window, cx);
11187    });
11188    cx.assert_editor_state(indoc! {r#"
11189        def main():
11190            items = ["''", 'ˇ']
11191    "#});
11192
11193    // Mixed quotes on same line
11194    cx.set_state(indoc! {r#"
11195        def main():
11196            items = ['"""', "'''''", ˇ]
11197    "#});
11198    cx.update_editor(|editor, window, cx| {
11199        editor.handle_input("\"", window, cx);
11200    });
11201    cx.assert_editor_state(indoc! {r#"
11202        def main():
11203            items = ['"""', "'''''", "ˇ"]
11204    "#});
11205    cx.update_editor(|editor, window, cx| {
11206        editor.move_right(&MoveRight, window, cx);
11207    });
11208    cx.update_editor(|editor, window, cx| {
11209        editor.handle_input(", ", window, cx);
11210    });
11211    cx.update_editor(|editor, window, cx| {
11212        editor.handle_input("'", window, cx);
11213    });
11214    cx.assert_editor_state(indoc! {r#"
11215        def main():
11216            items = ['"""', "'''''", "", 'ˇ']
11217    "#});
11218}
11219
11220#[gpui::test]
11221async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
11222    init_test(cx, |_| {});
11223
11224    let mut cx = EditorTestContext::new(cx).await;
11225    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
11226    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11227
11228    cx.set_state(indoc! {r#"
11229        def main():
11230            items = ["🎉", ˇ]
11231    "#});
11232    cx.update_editor(|editor, window, cx| {
11233        editor.handle_input("\"", window, cx);
11234    });
11235    cx.assert_editor_state(indoc! {r#"
11236        def main():
11237            items = ["🎉", "ˇ"]
11238    "#});
11239}
11240
11241#[gpui::test]
11242async fn test_surround_with_pair(cx: &mut TestAppContext) {
11243    init_test(cx, |_| {});
11244
11245    let language = Arc::new(Language::new(
11246        LanguageConfig {
11247            brackets: BracketPairConfig {
11248                pairs: vec![
11249                    BracketPair {
11250                        start: "{".to_string(),
11251                        end: "}".to_string(),
11252                        close: true,
11253                        surround: true,
11254                        newline: true,
11255                    },
11256                    BracketPair {
11257                        start: "/* ".to_string(),
11258                        end: "*/".to_string(),
11259                        close: true,
11260                        surround: true,
11261                        ..Default::default()
11262                    },
11263                ],
11264                ..Default::default()
11265            },
11266            ..Default::default()
11267        },
11268        Some(tree_sitter_rust::LANGUAGE.into()),
11269    ));
11270
11271    let text = r#"
11272        a
11273        b
11274        c
11275    "#
11276    .unindent();
11277
11278    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11279    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11280    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11281    editor
11282        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11283        .await;
11284
11285    editor.update_in(cx, |editor, window, cx| {
11286        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11287            s.select_display_ranges([
11288                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11289                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11290                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
11291            ])
11292        });
11293
11294        editor.handle_input("{", window, cx);
11295        editor.handle_input("{", window, cx);
11296        editor.handle_input("{", window, cx);
11297        assert_eq!(
11298            editor.text(cx),
11299            "
11300                {{{a}}}
11301                {{{b}}}
11302                {{{c}}}
11303            "
11304            .unindent()
11305        );
11306        assert_eq!(
11307            display_ranges(editor, cx),
11308            [
11309                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
11310                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
11311                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
11312            ]
11313        );
11314
11315        editor.undo(&Undo, window, cx);
11316        editor.undo(&Undo, window, cx);
11317        editor.undo(&Undo, window, cx);
11318        assert_eq!(
11319            editor.text(cx),
11320            "
11321                a
11322                b
11323                c
11324            "
11325            .unindent()
11326        );
11327        assert_eq!(
11328            display_ranges(editor, cx),
11329            [
11330                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11331                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11332                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11333            ]
11334        );
11335
11336        // Ensure inserting the first character of a multi-byte bracket pair
11337        // doesn't surround the selections with the bracket.
11338        editor.handle_input("/", window, cx);
11339        assert_eq!(
11340            editor.text(cx),
11341            "
11342                /
11343                /
11344                /
11345            "
11346            .unindent()
11347        );
11348        assert_eq!(
11349            display_ranges(editor, cx),
11350            [
11351                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11352                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11353                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11354            ]
11355        );
11356
11357        editor.undo(&Undo, window, cx);
11358        assert_eq!(
11359            editor.text(cx),
11360            "
11361                a
11362                b
11363                c
11364            "
11365            .unindent()
11366        );
11367        assert_eq!(
11368            display_ranges(editor, cx),
11369            [
11370                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11371                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11372                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11373            ]
11374        );
11375
11376        // Ensure inserting the last character of a multi-byte bracket pair
11377        // doesn't surround the selections with the bracket.
11378        editor.handle_input("*", window, cx);
11379        assert_eq!(
11380            editor.text(cx),
11381            "
11382                *
11383                *
11384                *
11385            "
11386            .unindent()
11387        );
11388        assert_eq!(
11389            display_ranges(editor, cx),
11390            [
11391                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11392                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11393                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11394            ]
11395        );
11396    });
11397}
11398
11399#[gpui::test]
11400async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
11401    init_test(cx, |_| {});
11402
11403    let language = Arc::new(Language::new(
11404        LanguageConfig {
11405            brackets: BracketPairConfig {
11406                pairs: vec![BracketPair {
11407                    start: "{".to_string(),
11408                    end: "}".to_string(),
11409                    close: true,
11410                    surround: true,
11411                    newline: true,
11412                }],
11413                ..Default::default()
11414            },
11415            autoclose_before: "}".to_string(),
11416            ..Default::default()
11417        },
11418        Some(tree_sitter_rust::LANGUAGE.into()),
11419    ));
11420
11421    let text = r#"
11422        a
11423        b
11424        c
11425    "#
11426    .unindent();
11427
11428    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11429    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11430    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11431    editor
11432        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11433        .await;
11434
11435    editor.update_in(cx, |editor, window, cx| {
11436        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11437            s.select_ranges([
11438                Point::new(0, 1)..Point::new(0, 1),
11439                Point::new(1, 1)..Point::new(1, 1),
11440                Point::new(2, 1)..Point::new(2, 1),
11441            ])
11442        });
11443
11444        editor.handle_input("{", window, cx);
11445        editor.handle_input("{", window, cx);
11446        editor.handle_input("_", window, cx);
11447        assert_eq!(
11448            editor.text(cx),
11449            "
11450                a{{_}}
11451                b{{_}}
11452                c{{_}}
11453            "
11454            .unindent()
11455        );
11456        assert_eq!(
11457            editor
11458                .selections
11459                .ranges::<Point>(&editor.display_snapshot(cx)),
11460            [
11461                Point::new(0, 4)..Point::new(0, 4),
11462                Point::new(1, 4)..Point::new(1, 4),
11463                Point::new(2, 4)..Point::new(2, 4)
11464            ]
11465        );
11466
11467        editor.backspace(&Default::default(), window, cx);
11468        editor.backspace(&Default::default(), window, cx);
11469        assert_eq!(
11470            editor.text(cx),
11471            "
11472                a{}
11473                b{}
11474                c{}
11475            "
11476            .unindent()
11477        );
11478        assert_eq!(
11479            editor
11480                .selections
11481                .ranges::<Point>(&editor.display_snapshot(cx)),
11482            [
11483                Point::new(0, 2)..Point::new(0, 2),
11484                Point::new(1, 2)..Point::new(1, 2),
11485                Point::new(2, 2)..Point::new(2, 2)
11486            ]
11487        );
11488
11489        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11490        assert_eq!(
11491            editor.text(cx),
11492            "
11493                a
11494                b
11495                c
11496            "
11497            .unindent()
11498        );
11499        assert_eq!(
11500            editor
11501                .selections
11502                .ranges::<Point>(&editor.display_snapshot(cx)),
11503            [
11504                Point::new(0, 1)..Point::new(0, 1),
11505                Point::new(1, 1)..Point::new(1, 1),
11506                Point::new(2, 1)..Point::new(2, 1)
11507            ]
11508        );
11509    });
11510}
11511
11512#[gpui::test]
11513async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11514    init_test(cx, |settings| {
11515        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11516    });
11517
11518    let mut cx = EditorTestContext::new(cx).await;
11519
11520    let language = Arc::new(Language::new(
11521        LanguageConfig {
11522            brackets: BracketPairConfig {
11523                pairs: vec![
11524                    BracketPair {
11525                        start: "{".to_string(),
11526                        end: "}".to_string(),
11527                        close: true,
11528                        surround: true,
11529                        newline: true,
11530                    },
11531                    BracketPair {
11532                        start: "(".to_string(),
11533                        end: ")".to_string(),
11534                        close: true,
11535                        surround: true,
11536                        newline: true,
11537                    },
11538                    BracketPair {
11539                        start: "[".to_string(),
11540                        end: "]".to_string(),
11541                        close: false,
11542                        surround: true,
11543                        newline: true,
11544                    },
11545                ],
11546                ..Default::default()
11547            },
11548            autoclose_before: "})]".to_string(),
11549            ..Default::default()
11550        },
11551        Some(tree_sitter_rust::LANGUAGE.into()),
11552    ));
11553
11554    cx.language_registry().add(language.clone());
11555    cx.update_buffer(|buffer, cx| {
11556        buffer.set_language(Some(language), cx);
11557    });
11558
11559    cx.set_state(
11560        &"
11561            {(ˇ)}
11562            [[ˇ]]
11563            {(ˇ)}
11564        "
11565        .unindent(),
11566    );
11567
11568    cx.update_editor(|editor, window, cx| {
11569        editor.backspace(&Default::default(), window, cx);
11570        editor.backspace(&Default::default(), window, cx);
11571    });
11572
11573    cx.assert_editor_state(
11574        &"
11575            ˇ
11576            ˇ]]
11577            ˇ
11578        "
11579        .unindent(),
11580    );
11581
11582    cx.update_editor(|editor, window, cx| {
11583        editor.handle_input("{", window, cx);
11584        editor.handle_input("{", window, cx);
11585        editor.move_right(&MoveRight, window, cx);
11586        editor.move_right(&MoveRight, window, cx);
11587        editor.move_left(&MoveLeft, window, cx);
11588        editor.move_left(&MoveLeft, window, cx);
11589        editor.backspace(&Default::default(), window, cx);
11590    });
11591
11592    cx.assert_editor_state(
11593        &"
11594            {ˇ}
11595            {ˇ}]]
11596            {ˇ}
11597        "
11598        .unindent(),
11599    );
11600
11601    cx.update_editor(|editor, window, cx| {
11602        editor.backspace(&Default::default(), window, cx);
11603    });
11604
11605    cx.assert_editor_state(
11606        &"
11607            ˇ
11608            ˇ]]
11609            ˇ
11610        "
11611        .unindent(),
11612    );
11613}
11614
11615#[gpui::test]
11616async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11617    init_test(cx, |_| {});
11618
11619    let language = Arc::new(Language::new(
11620        LanguageConfig::default(),
11621        Some(tree_sitter_rust::LANGUAGE.into()),
11622    ));
11623
11624    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11625    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11626    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11627    editor
11628        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11629        .await;
11630
11631    editor.update_in(cx, |editor, window, cx| {
11632        editor.set_auto_replace_emoji_shortcode(true);
11633
11634        editor.handle_input("Hello ", window, cx);
11635        editor.handle_input(":wave", window, cx);
11636        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11637
11638        editor.handle_input(":", window, cx);
11639        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11640
11641        editor.handle_input(" :smile", window, cx);
11642        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11643
11644        editor.handle_input(":", window, cx);
11645        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11646
11647        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11648        editor.handle_input(":wave", window, cx);
11649        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11650
11651        editor.handle_input(":", window, cx);
11652        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11653
11654        editor.handle_input(":1", window, cx);
11655        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11656
11657        editor.handle_input(":", window, cx);
11658        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11659
11660        // Ensure shortcode does not get replaced when it is part of a word
11661        editor.handle_input(" Test:wave", window, cx);
11662        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11663
11664        editor.handle_input(":", window, cx);
11665        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11666
11667        editor.set_auto_replace_emoji_shortcode(false);
11668
11669        // Ensure shortcode does not get replaced when auto replace is off
11670        editor.handle_input(" :wave", window, cx);
11671        assert_eq!(
11672            editor.text(cx),
11673            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11674        );
11675
11676        editor.handle_input(":", window, cx);
11677        assert_eq!(
11678            editor.text(cx),
11679            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11680        );
11681    });
11682}
11683
11684#[gpui::test]
11685async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11686    init_test(cx, |_| {});
11687
11688    let (text, insertion_ranges) = marked_text_ranges(
11689        indoc! {"
11690            ˇ
11691        "},
11692        false,
11693    );
11694
11695    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11696    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11697
11698    _ = editor.update_in(cx, |editor, window, cx| {
11699        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11700
11701        editor
11702            .insert_snippet(
11703                &insertion_ranges
11704                    .iter()
11705                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11706                    .collect::<Vec<_>>(),
11707                snippet,
11708                window,
11709                cx,
11710            )
11711            .unwrap();
11712
11713        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11714            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11715            assert_eq!(editor.text(cx), expected_text);
11716            assert_eq!(
11717                editor.selections.ranges(&editor.display_snapshot(cx)),
11718                selection_ranges
11719                    .iter()
11720                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11721                    .collect::<Vec<_>>()
11722            );
11723        }
11724
11725        assert(
11726            editor,
11727            cx,
11728            indoc! {"
11729            type «» =•
11730            "},
11731        );
11732
11733        assert!(editor.context_menu_visible(), "There should be a matches");
11734    });
11735}
11736
11737#[gpui::test]
11738async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11739    init_test(cx, |_| {});
11740
11741    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11742        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11743        assert_eq!(editor.text(cx), expected_text);
11744        assert_eq!(
11745            editor.selections.ranges(&editor.display_snapshot(cx)),
11746            selection_ranges
11747                .iter()
11748                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11749                .collect::<Vec<_>>()
11750        );
11751    }
11752
11753    let (text, insertion_ranges) = marked_text_ranges(
11754        indoc! {"
11755            ˇ
11756        "},
11757        false,
11758    );
11759
11760    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11761    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11762
11763    _ = editor.update_in(cx, |editor, window, cx| {
11764        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11765
11766        editor
11767            .insert_snippet(
11768                &insertion_ranges
11769                    .iter()
11770                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11771                    .collect::<Vec<_>>(),
11772                snippet,
11773                window,
11774                cx,
11775            )
11776            .unwrap();
11777
11778        assert_state(
11779            editor,
11780            cx,
11781            indoc! {"
11782            type «» = ;•
11783            "},
11784        );
11785
11786        assert!(
11787            editor.context_menu_visible(),
11788            "Context menu should be visible for placeholder choices"
11789        );
11790
11791        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11792
11793        assert_state(
11794            editor,
11795            cx,
11796            indoc! {"
11797            type  = «»;•
11798            "},
11799        );
11800
11801        assert!(
11802            !editor.context_menu_visible(),
11803            "Context menu should be hidden after moving to next tabstop"
11804        );
11805
11806        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11807
11808        assert_state(
11809            editor,
11810            cx,
11811            indoc! {"
11812            type  = ; ˇ
11813            "},
11814        );
11815
11816        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11817
11818        assert_state(
11819            editor,
11820            cx,
11821            indoc! {"
11822            type  = ; ˇ
11823            "},
11824        );
11825    });
11826
11827    _ = editor.update_in(cx, |editor, window, cx| {
11828        editor.select_all(&SelectAll, window, cx);
11829        editor.backspace(&Backspace, window, cx);
11830
11831        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11832        let insertion_ranges = editor
11833            .selections
11834            .all(&editor.display_snapshot(cx))
11835            .iter()
11836            .map(|s| s.range())
11837            .collect::<Vec<_>>();
11838
11839        editor
11840            .insert_snippet(&insertion_ranges, snippet, window, cx)
11841            .unwrap();
11842
11843        assert_state(editor, cx, "fn «» = value;•");
11844
11845        assert!(
11846            editor.context_menu_visible(),
11847            "Context menu should be visible for placeholder choices"
11848        );
11849
11850        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11851
11852        assert_state(editor, cx, "fn  = «valueˇ»;•");
11853
11854        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11855
11856        assert_state(editor, cx, "fn «» = value;•");
11857
11858        assert!(
11859            editor.context_menu_visible(),
11860            "Context menu should be visible again after returning to first tabstop"
11861        );
11862
11863        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11864
11865        assert_state(editor, cx, "fn «» = value;•");
11866    });
11867}
11868
11869#[gpui::test]
11870async fn test_snippets(cx: &mut TestAppContext) {
11871    init_test(cx, |_| {});
11872
11873    let mut cx = EditorTestContext::new(cx).await;
11874
11875    cx.set_state(indoc! {"
11876        a.ˇ b
11877        a.ˇ b
11878        a.ˇ b
11879    "});
11880
11881    cx.update_editor(|editor, window, cx| {
11882        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11883        let insertion_ranges = editor
11884            .selections
11885            .all(&editor.display_snapshot(cx))
11886            .iter()
11887            .map(|s| s.range())
11888            .collect::<Vec<_>>();
11889        editor
11890            .insert_snippet(&insertion_ranges, snippet, window, cx)
11891            .unwrap();
11892    });
11893
11894    cx.assert_editor_state(indoc! {"
11895        a.f(«oneˇ», two, «threeˇ») b
11896        a.f(«oneˇ», two, «threeˇ») b
11897        a.f(«oneˇ», two, «threeˇ») b
11898    "});
11899
11900    // Can't move earlier than the first tab stop
11901    cx.update_editor(|editor, window, cx| {
11902        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11903    });
11904    cx.assert_editor_state(indoc! {"
11905        a.f(«oneˇ», two, «threeˇ») b
11906        a.f(«oneˇ», two, «threeˇ») b
11907        a.f(«oneˇ», two, «threeˇ») b
11908    "});
11909
11910    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11911    cx.assert_editor_state(indoc! {"
11912        a.f(one, «twoˇ», three) b
11913        a.f(one, «twoˇ», three) b
11914        a.f(one, «twoˇ», three) b
11915    "});
11916
11917    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11918    cx.assert_editor_state(indoc! {"
11919        a.f(«oneˇ», two, «threeˇ») b
11920        a.f(«oneˇ», two, «threeˇ») b
11921        a.f(«oneˇ», two, «threeˇ») b
11922    "});
11923
11924    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11925    cx.assert_editor_state(indoc! {"
11926        a.f(one, «twoˇ», three) b
11927        a.f(one, «twoˇ», three) b
11928        a.f(one, «twoˇ», three) b
11929    "});
11930    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11931    cx.assert_editor_state(indoc! {"
11932        a.f(one, two, three)ˇ b
11933        a.f(one, two, three)ˇ b
11934        a.f(one, two, three)ˇ b
11935    "});
11936
11937    // As soon as the last tab stop is reached, snippet state is gone
11938    cx.update_editor(|editor, window, cx| {
11939        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11940    });
11941    cx.assert_editor_state(indoc! {"
11942        a.f(one, two, three)ˇ b
11943        a.f(one, two, three)ˇ b
11944        a.f(one, two, three)ˇ b
11945    "});
11946}
11947
11948#[gpui::test]
11949async fn test_snippet_indentation(cx: &mut TestAppContext) {
11950    init_test(cx, |_| {});
11951
11952    let mut cx = EditorTestContext::new(cx).await;
11953
11954    cx.update_editor(|editor, window, cx| {
11955        let snippet = Snippet::parse(indoc! {"
11956            /*
11957             * Multiline comment with leading indentation
11958             *
11959             * $1
11960             */
11961            $0"})
11962        .unwrap();
11963        let insertion_ranges = editor
11964            .selections
11965            .all(&editor.display_snapshot(cx))
11966            .iter()
11967            .map(|s| s.range())
11968            .collect::<Vec<_>>();
11969        editor
11970            .insert_snippet(&insertion_ranges, snippet, window, cx)
11971            .unwrap();
11972    });
11973
11974    cx.assert_editor_state(indoc! {"
11975        /*
11976         * Multiline comment with leading indentation
11977         *
11978         * ˇ
11979         */
11980    "});
11981
11982    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11983    cx.assert_editor_state(indoc! {"
11984        /*
11985         * Multiline comment with leading indentation
11986         *
11987         *•
11988         */
11989        ˇ"});
11990}
11991
11992#[gpui::test]
11993async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11994    init_test(cx, |_| {});
11995
11996    let mut cx = EditorTestContext::new(cx).await;
11997    cx.update_editor(|editor, _, cx| {
11998        editor.project().unwrap().update(cx, |project, cx| {
11999            project.snippets().update(cx, |snippets, _cx| {
12000                let snippet = project::snippet_provider::Snippet {
12001                    prefix: vec!["multi word".to_string()],
12002                    body: "this is many words".to_string(),
12003                    description: Some("description".to_string()),
12004                    name: "multi-word snippet test".to_string(),
12005                };
12006                snippets.add_snippet_for_test(
12007                    None,
12008                    PathBuf::from("test_snippets.json"),
12009                    vec![Arc::new(snippet)],
12010                );
12011            });
12012        })
12013    });
12014
12015    for (input_to_simulate, should_match_snippet) in [
12016        ("m", true),
12017        ("m ", true),
12018        ("m w", true),
12019        ("aa m w", true),
12020        ("aa m g", false),
12021    ] {
12022        cx.set_state("ˇ");
12023        cx.simulate_input(input_to_simulate); // fails correctly
12024
12025        cx.update_editor(|editor, _, _| {
12026            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
12027            else {
12028                assert!(!should_match_snippet); // no completions! don't even show the menu
12029                return;
12030            };
12031            assert!(context_menu.visible());
12032            let completions = context_menu.completions.borrow();
12033
12034            assert_eq!(!completions.is_empty(), should_match_snippet);
12035        });
12036    }
12037}
12038
12039#[gpui::test]
12040async fn test_document_format_during_save(cx: &mut TestAppContext) {
12041    init_test(cx, |_| {});
12042
12043    let fs = FakeFs::new(cx.executor());
12044    fs.insert_file(path!("/file.rs"), Default::default()).await;
12045
12046    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
12047
12048    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12049    language_registry.add(rust_lang());
12050    let mut fake_servers = language_registry.register_fake_lsp(
12051        "Rust",
12052        FakeLspAdapter {
12053            capabilities: lsp::ServerCapabilities {
12054                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12055                ..Default::default()
12056            },
12057            ..Default::default()
12058        },
12059    );
12060
12061    let buffer = project
12062        .update(cx, |project, cx| {
12063            project.open_local_buffer(path!("/file.rs"), cx)
12064        })
12065        .await
12066        .unwrap();
12067
12068    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12069    let (editor, cx) = cx.add_window_view(|window, cx| {
12070        build_editor_with_project(project.clone(), buffer, window, cx)
12071    });
12072    editor.update_in(cx, |editor, window, cx| {
12073        editor.set_text("one\ntwo\nthree\n", window, cx)
12074    });
12075    assert!(cx.read(|cx| editor.is_dirty(cx)));
12076
12077    let fake_server = fake_servers.next().await.unwrap();
12078
12079    {
12080        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12081            move |params, _| async move {
12082                assert_eq!(
12083                    params.text_document.uri,
12084                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12085                );
12086                assert_eq!(params.options.tab_size, 4);
12087                Ok(Some(vec![lsp::TextEdit::new(
12088                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12089                    ", ".to_string(),
12090                )]))
12091            },
12092        );
12093        let save = editor
12094            .update_in(cx, |editor, window, cx| {
12095                editor.save(
12096                    SaveOptions {
12097                        format: true,
12098                        autosave: false,
12099                    },
12100                    project.clone(),
12101                    window,
12102                    cx,
12103                )
12104            })
12105            .unwrap();
12106        save.await;
12107
12108        assert_eq!(
12109            editor.update(cx, |editor, cx| editor.text(cx)),
12110            "one, two\nthree\n"
12111        );
12112        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12113    }
12114
12115    {
12116        editor.update_in(cx, |editor, window, cx| {
12117            editor.set_text("one\ntwo\nthree\n", window, cx)
12118        });
12119        assert!(cx.read(|cx| editor.is_dirty(cx)));
12120
12121        // Ensure we can still save even if formatting hangs.
12122        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12123            move |params, _| async move {
12124                assert_eq!(
12125                    params.text_document.uri,
12126                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12127                );
12128                futures::future::pending::<()>().await;
12129                unreachable!()
12130            },
12131        );
12132        let save = editor
12133            .update_in(cx, |editor, window, cx| {
12134                editor.save(
12135                    SaveOptions {
12136                        format: true,
12137                        autosave: false,
12138                    },
12139                    project.clone(),
12140                    window,
12141                    cx,
12142                )
12143            })
12144            .unwrap();
12145        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12146        save.await;
12147        assert_eq!(
12148            editor.update(cx, |editor, cx| editor.text(cx)),
12149            "one\ntwo\nthree\n"
12150        );
12151    }
12152
12153    // Set rust language override and assert overridden tabsize is sent to language server
12154    update_test_language_settings(cx, |settings| {
12155        settings.languages.0.insert(
12156            "Rust".into(),
12157            LanguageSettingsContent {
12158                tab_size: NonZeroU32::new(8),
12159                ..Default::default()
12160            },
12161        );
12162    });
12163
12164    {
12165        editor.update_in(cx, |editor, window, cx| {
12166            editor.set_text("somehting_new\n", window, cx)
12167        });
12168        assert!(cx.read(|cx| editor.is_dirty(cx)));
12169        let _formatting_request_signal = fake_server
12170            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12171                assert_eq!(
12172                    params.text_document.uri,
12173                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12174                );
12175                assert_eq!(params.options.tab_size, 8);
12176                Ok(Some(vec![]))
12177            });
12178        let save = editor
12179            .update_in(cx, |editor, window, cx| {
12180                editor.save(
12181                    SaveOptions {
12182                        format: true,
12183                        autosave: false,
12184                    },
12185                    project.clone(),
12186                    window,
12187                    cx,
12188                )
12189            })
12190            .unwrap();
12191        save.await;
12192    }
12193}
12194
12195#[gpui::test]
12196async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
12197    init_test(cx, |settings| {
12198        settings.defaults.ensure_final_newline_on_save = Some(false);
12199    });
12200
12201    let fs = FakeFs::new(cx.executor());
12202    fs.insert_file(path!("/file.txt"), "foo".into()).await;
12203
12204    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
12205
12206    let buffer = project
12207        .update(cx, |project, cx| {
12208            project.open_local_buffer(path!("/file.txt"), cx)
12209        })
12210        .await
12211        .unwrap();
12212
12213    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12214    let (editor, cx) = cx.add_window_view(|window, cx| {
12215        build_editor_with_project(project.clone(), buffer, window, cx)
12216    });
12217    editor.update_in(cx, |editor, window, cx| {
12218        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
12219            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
12220        });
12221    });
12222    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12223
12224    editor.update_in(cx, |editor, window, cx| {
12225        editor.handle_input("\n", window, cx)
12226    });
12227    cx.run_until_parked();
12228    save(&editor, &project, cx).await;
12229    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12230
12231    editor.update_in(cx, |editor, window, cx| {
12232        editor.undo(&Default::default(), window, cx);
12233    });
12234    save(&editor, &project, cx).await;
12235    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12236
12237    editor.update_in(cx, |editor, window, cx| {
12238        editor.redo(&Default::default(), window, cx);
12239    });
12240    cx.run_until_parked();
12241    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12242
12243    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
12244        let save = editor
12245            .update_in(cx, |editor, window, cx| {
12246                editor.save(
12247                    SaveOptions {
12248                        format: true,
12249                        autosave: false,
12250                    },
12251                    project.clone(),
12252                    window,
12253                    cx,
12254                )
12255            })
12256            .unwrap();
12257        save.await;
12258        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12259    }
12260}
12261
12262#[gpui::test]
12263async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
12264    init_test(cx, |_| {});
12265
12266    let cols = 4;
12267    let rows = 10;
12268    let sample_text_1 = sample_text(rows, cols, 'a');
12269    assert_eq!(
12270        sample_text_1,
12271        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12272    );
12273    let sample_text_2 = sample_text(rows, cols, 'l');
12274    assert_eq!(
12275        sample_text_2,
12276        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12277    );
12278    let sample_text_3 = sample_text(rows, cols, 'v');
12279    assert_eq!(
12280        sample_text_3,
12281        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12282    );
12283
12284    let fs = FakeFs::new(cx.executor());
12285    fs.insert_tree(
12286        path!("/a"),
12287        json!({
12288            "main.rs": sample_text_1,
12289            "other.rs": sample_text_2,
12290            "lib.rs": sample_text_3,
12291        }),
12292    )
12293    .await;
12294
12295    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12296    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12297    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12298
12299    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12300    language_registry.add(rust_lang());
12301    let mut fake_servers = language_registry.register_fake_lsp(
12302        "Rust",
12303        FakeLspAdapter {
12304            capabilities: lsp::ServerCapabilities {
12305                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12306                ..Default::default()
12307            },
12308            ..Default::default()
12309        },
12310    );
12311
12312    let worktree = project.update(cx, |project, cx| {
12313        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
12314        assert_eq!(worktrees.len(), 1);
12315        worktrees.pop().unwrap()
12316    });
12317    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12318
12319    let buffer_1 = project
12320        .update(cx, |project, cx| {
12321            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
12322        })
12323        .await
12324        .unwrap();
12325    let buffer_2 = project
12326        .update(cx, |project, cx| {
12327            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
12328        })
12329        .await
12330        .unwrap();
12331    let buffer_3 = project
12332        .update(cx, |project, cx| {
12333            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
12334        })
12335        .await
12336        .unwrap();
12337
12338    let multi_buffer = cx.new(|cx| {
12339        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12340        multi_buffer.push_excerpts(
12341            buffer_1.clone(),
12342            [
12343                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12344                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12345                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12346            ],
12347            cx,
12348        );
12349        multi_buffer.push_excerpts(
12350            buffer_2.clone(),
12351            [
12352                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12353                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12354                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12355            ],
12356            cx,
12357        );
12358        multi_buffer.push_excerpts(
12359            buffer_3.clone(),
12360            [
12361                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12362                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12363                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12364            ],
12365            cx,
12366        );
12367        multi_buffer
12368    });
12369    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12370        Editor::new(
12371            EditorMode::full(),
12372            multi_buffer,
12373            Some(project.clone()),
12374            window,
12375            cx,
12376        )
12377    });
12378
12379    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12380        editor.change_selections(
12381            SelectionEffects::scroll(Autoscroll::Next),
12382            window,
12383            cx,
12384            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
12385        );
12386        editor.insert("|one|two|three|", window, cx);
12387    });
12388    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12389    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12390        editor.change_selections(
12391            SelectionEffects::scroll(Autoscroll::Next),
12392            window,
12393            cx,
12394            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
12395        );
12396        editor.insert("|four|five|six|", window, cx);
12397    });
12398    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12399
12400    // First two buffers should be edited, but not the third one.
12401    assert_eq!(
12402        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12403        "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}",
12404    );
12405    buffer_1.update(cx, |buffer, _| {
12406        assert!(buffer.is_dirty());
12407        assert_eq!(
12408            buffer.text(),
12409            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
12410        )
12411    });
12412    buffer_2.update(cx, |buffer, _| {
12413        assert!(buffer.is_dirty());
12414        assert_eq!(
12415            buffer.text(),
12416            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
12417        )
12418    });
12419    buffer_3.update(cx, |buffer, _| {
12420        assert!(!buffer.is_dirty());
12421        assert_eq!(buffer.text(), sample_text_3,)
12422    });
12423    cx.executor().run_until_parked();
12424
12425    let save = multi_buffer_editor
12426        .update_in(cx, |editor, window, cx| {
12427            editor.save(
12428                SaveOptions {
12429                    format: true,
12430                    autosave: false,
12431                },
12432                project.clone(),
12433                window,
12434                cx,
12435            )
12436        })
12437        .unwrap();
12438
12439    let fake_server = fake_servers.next().await.unwrap();
12440    fake_server
12441        .server
12442        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
12443            Ok(Some(vec![lsp::TextEdit::new(
12444                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12445                format!("[{} formatted]", params.text_document.uri),
12446            )]))
12447        })
12448        .detach();
12449    save.await;
12450
12451    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
12452    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
12453    assert_eq!(
12454        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12455        uri!(
12456            "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}"
12457        ),
12458    );
12459    buffer_1.update(cx, |buffer, _| {
12460        assert!(!buffer.is_dirty());
12461        assert_eq!(
12462            buffer.text(),
12463            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
12464        )
12465    });
12466    buffer_2.update(cx, |buffer, _| {
12467        assert!(!buffer.is_dirty());
12468        assert_eq!(
12469            buffer.text(),
12470            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
12471        )
12472    });
12473    buffer_3.update(cx, |buffer, _| {
12474        assert!(!buffer.is_dirty());
12475        assert_eq!(buffer.text(), sample_text_3,)
12476    });
12477}
12478
12479#[gpui::test]
12480async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12481    init_test(cx, |_| {});
12482
12483    let fs = FakeFs::new(cx.executor());
12484    fs.insert_tree(
12485        path!("/dir"),
12486        json!({
12487            "file1.rs": "fn main() { println!(\"hello\"); }",
12488            "file2.rs": "fn test() { println!(\"test\"); }",
12489            "file3.rs": "fn other() { println!(\"other\"); }\n",
12490        }),
12491    )
12492    .await;
12493
12494    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12495    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12496    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12497
12498    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12499    language_registry.add(rust_lang());
12500
12501    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12502    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12503
12504    // Open three buffers
12505    let buffer_1 = project
12506        .update(cx, |project, cx| {
12507            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12508        })
12509        .await
12510        .unwrap();
12511    let buffer_2 = project
12512        .update(cx, |project, cx| {
12513            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12514        })
12515        .await
12516        .unwrap();
12517    let buffer_3 = project
12518        .update(cx, |project, cx| {
12519            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12520        })
12521        .await
12522        .unwrap();
12523
12524    // Create a multi-buffer with all three buffers
12525    let multi_buffer = cx.new(|cx| {
12526        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12527        multi_buffer.push_excerpts(
12528            buffer_1.clone(),
12529            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12530            cx,
12531        );
12532        multi_buffer.push_excerpts(
12533            buffer_2.clone(),
12534            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12535            cx,
12536        );
12537        multi_buffer.push_excerpts(
12538            buffer_3.clone(),
12539            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12540            cx,
12541        );
12542        multi_buffer
12543    });
12544
12545    let editor = cx.new_window_entity(|window, cx| {
12546        Editor::new(
12547            EditorMode::full(),
12548            multi_buffer,
12549            Some(project.clone()),
12550            window,
12551            cx,
12552        )
12553    });
12554
12555    // Edit only the first buffer
12556    editor.update_in(cx, |editor, window, cx| {
12557        editor.change_selections(
12558            SelectionEffects::scroll(Autoscroll::Next),
12559            window,
12560            cx,
12561            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12562        );
12563        editor.insert("// edited", window, cx);
12564    });
12565
12566    // Verify that only buffer 1 is dirty
12567    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12568    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12569    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12570
12571    // Get write counts after file creation (files were created with initial content)
12572    // We expect each file to have been written once during creation
12573    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12574    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12575    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12576
12577    // Perform autosave
12578    let save_task = editor.update_in(cx, |editor, window, cx| {
12579        editor.save(
12580            SaveOptions {
12581                format: true,
12582                autosave: true,
12583            },
12584            project.clone(),
12585            window,
12586            cx,
12587        )
12588    });
12589    save_task.await.unwrap();
12590
12591    // Only the dirty buffer should have been saved
12592    assert_eq!(
12593        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12594        1,
12595        "Buffer 1 was dirty, so it should have been written once during autosave"
12596    );
12597    assert_eq!(
12598        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12599        0,
12600        "Buffer 2 was clean, so it should not have been written during autosave"
12601    );
12602    assert_eq!(
12603        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12604        0,
12605        "Buffer 3 was clean, so it should not have been written during autosave"
12606    );
12607
12608    // Verify buffer states after autosave
12609    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12610    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12611    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12612
12613    // Now perform a manual save (format = true)
12614    let save_task = editor.update_in(cx, |editor, window, cx| {
12615        editor.save(
12616            SaveOptions {
12617                format: true,
12618                autosave: false,
12619            },
12620            project.clone(),
12621            window,
12622            cx,
12623        )
12624    });
12625    save_task.await.unwrap();
12626
12627    // During manual save, clean buffers don't get written to disk
12628    // They just get did_save called for language server notifications
12629    assert_eq!(
12630        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12631        1,
12632        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12633    );
12634    assert_eq!(
12635        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12636        0,
12637        "Buffer 2 should not have been written at all"
12638    );
12639    assert_eq!(
12640        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12641        0,
12642        "Buffer 3 should not have been written at all"
12643    );
12644}
12645
12646async fn setup_range_format_test(
12647    cx: &mut TestAppContext,
12648) -> (
12649    Entity<Project>,
12650    Entity<Editor>,
12651    &mut gpui::VisualTestContext,
12652    lsp::FakeLanguageServer,
12653) {
12654    init_test(cx, |_| {});
12655
12656    let fs = FakeFs::new(cx.executor());
12657    fs.insert_file(path!("/file.rs"), Default::default()).await;
12658
12659    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12660
12661    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12662    language_registry.add(rust_lang());
12663    let mut fake_servers = language_registry.register_fake_lsp(
12664        "Rust",
12665        FakeLspAdapter {
12666            capabilities: lsp::ServerCapabilities {
12667                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12668                ..lsp::ServerCapabilities::default()
12669            },
12670            ..FakeLspAdapter::default()
12671        },
12672    );
12673
12674    let buffer = project
12675        .update(cx, |project, cx| {
12676            project.open_local_buffer(path!("/file.rs"), cx)
12677        })
12678        .await
12679        .unwrap();
12680
12681    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12682    let (editor, cx) = cx.add_window_view(|window, cx| {
12683        build_editor_with_project(project.clone(), buffer, window, cx)
12684    });
12685
12686    let fake_server = fake_servers.next().await.unwrap();
12687
12688    (project, editor, cx, fake_server)
12689}
12690
12691#[gpui::test]
12692async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12693    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12694
12695    editor.update_in(cx, |editor, window, cx| {
12696        editor.set_text("one\ntwo\nthree\n", window, cx)
12697    });
12698    assert!(cx.read(|cx| editor.is_dirty(cx)));
12699
12700    let save = editor
12701        .update_in(cx, |editor, window, cx| {
12702            editor.save(
12703                SaveOptions {
12704                    format: true,
12705                    autosave: false,
12706                },
12707                project.clone(),
12708                window,
12709                cx,
12710            )
12711        })
12712        .unwrap();
12713    fake_server
12714        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12715            assert_eq!(
12716                params.text_document.uri,
12717                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12718            );
12719            assert_eq!(params.options.tab_size, 4);
12720            Ok(Some(vec![lsp::TextEdit::new(
12721                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12722                ", ".to_string(),
12723            )]))
12724        })
12725        .next()
12726        .await;
12727    save.await;
12728    assert_eq!(
12729        editor.update(cx, |editor, cx| editor.text(cx)),
12730        "one, two\nthree\n"
12731    );
12732    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12733}
12734
12735#[gpui::test]
12736async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12737    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12738
12739    editor.update_in(cx, |editor, window, cx| {
12740        editor.set_text("one\ntwo\nthree\n", window, cx)
12741    });
12742    assert!(cx.read(|cx| editor.is_dirty(cx)));
12743
12744    // Test that save still works when formatting hangs
12745    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12746        move |params, _| async move {
12747            assert_eq!(
12748                params.text_document.uri,
12749                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12750            );
12751            futures::future::pending::<()>().await;
12752            unreachable!()
12753        },
12754    );
12755    let save = editor
12756        .update_in(cx, |editor, window, cx| {
12757            editor.save(
12758                SaveOptions {
12759                    format: true,
12760                    autosave: false,
12761                },
12762                project.clone(),
12763                window,
12764                cx,
12765            )
12766        })
12767        .unwrap();
12768    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12769    save.await;
12770    assert_eq!(
12771        editor.update(cx, |editor, cx| editor.text(cx)),
12772        "one\ntwo\nthree\n"
12773    );
12774    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12775}
12776
12777#[gpui::test]
12778async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12779    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12780
12781    // Buffer starts clean, no formatting should be requested
12782    let save = editor
12783        .update_in(cx, |editor, window, cx| {
12784            editor.save(
12785                SaveOptions {
12786                    format: false,
12787                    autosave: false,
12788                },
12789                project.clone(),
12790                window,
12791                cx,
12792            )
12793        })
12794        .unwrap();
12795    let _pending_format_request = fake_server
12796        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12797            panic!("Should not be invoked");
12798        })
12799        .next();
12800    save.await;
12801    cx.run_until_parked();
12802}
12803
12804#[gpui::test]
12805async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12806    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12807
12808    // Set Rust language override and assert overridden tabsize is sent to language server
12809    update_test_language_settings(cx, |settings| {
12810        settings.languages.0.insert(
12811            "Rust".into(),
12812            LanguageSettingsContent {
12813                tab_size: NonZeroU32::new(8),
12814                ..Default::default()
12815            },
12816        );
12817    });
12818
12819    editor.update_in(cx, |editor, window, cx| {
12820        editor.set_text("something_new\n", window, cx)
12821    });
12822    assert!(cx.read(|cx| editor.is_dirty(cx)));
12823    let save = editor
12824        .update_in(cx, |editor, window, cx| {
12825            editor.save(
12826                SaveOptions {
12827                    format: true,
12828                    autosave: false,
12829                },
12830                project.clone(),
12831                window,
12832                cx,
12833            )
12834        })
12835        .unwrap();
12836    fake_server
12837        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12838            assert_eq!(
12839                params.text_document.uri,
12840                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12841            );
12842            assert_eq!(params.options.tab_size, 8);
12843            Ok(Some(Vec::new()))
12844        })
12845        .next()
12846        .await;
12847    save.await;
12848}
12849
12850#[gpui::test]
12851async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12852    init_test(cx, |settings| {
12853        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12854            settings::LanguageServerFormatterSpecifier::Current,
12855        )))
12856    });
12857
12858    let fs = FakeFs::new(cx.executor());
12859    fs.insert_file(path!("/file.rs"), Default::default()).await;
12860
12861    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12862
12863    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12864    language_registry.add(Arc::new(Language::new(
12865        LanguageConfig {
12866            name: "Rust".into(),
12867            matcher: LanguageMatcher {
12868                path_suffixes: vec!["rs".to_string()],
12869                ..Default::default()
12870            },
12871            ..LanguageConfig::default()
12872        },
12873        Some(tree_sitter_rust::LANGUAGE.into()),
12874    )));
12875    update_test_language_settings(cx, |settings| {
12876        // Enable Prettier formatting for the same buffer, and ensure
12877        // LSP is called instead of Prettier.
12878        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12879    });
12880    let mut fake_servers = language_registry.register_fake_lsp(
12881        "Rust",
12882        FakeLspAdapter {
12883            capabilities: lsp::ServerCapabilities {
12884                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12885                ..Default::default()
12886            },
12887            ..Default::default()
12888        },
12889    );
12890
12891    let buffer = project
12892        .update(cx, |project, cx| {
12893            project.open_local_buffer(path!("/file.rs"), cx)
12894        })
12895        .await
12896        .unwrap();
12897
12898    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12899    let (editor, cx) = cx.add_window_view(|window, cx| {
12900        build_editor_with_project(project.clone(), buffer, window, cx)
12901    });
12902    editor.update_in(cx, |editor, window, cx| {
12903        editor.set_text("one\ntwo\nthree\n", window, cx)
12904    });
12905
12906    let fake_server = fake_servers.next().await.unwrap();
12907
12908    let format = editor
12909        .update_in(cx, |editor, window, cx| {
12910            editor.perform_format(
12911                project.clone(),
12912                FormatTrigger::Manual,
12913                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12914                window,
12915                cx,
12916            )
12917        })
12918        .unwrap();
12919    fake_server
12920        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12921            assert_eq!(
12922                params.text_document.uri,
12923                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12924            );
12925            assert_eq!(params.options.tab_size, 4);
12926            Ok(Some(vec![lsp::TextEdit::new(
12927                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12928                ", ".to_string(),
12929            )]))
12930        })
12931        .next()
12932        .await;
12933    format.await;
12934    assert_eq!(
12935        editor.update(cx, |editor, cx| editor.text(cx)),
12936        "one, two\nthree\n"
12937    );
12938
12939    editor.update_in(cx, |editor, window, cx| {
12940        editor.set_text("one\ntwo\nthree\n", window, cx)
12941    });
12942    // Ensure we don't lock if formatting hangs.
12943    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12944        move |params, _| async move {
12945            assert_eq!(
12946                params.text_document.uri,
12947                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12948            );
12949            futures::future::pending::<()>().await;
12950            unreachable!()
12951        },
12952    );
12953    let format = editor
12954        .update_in(cx, |editor, window, cx| {
12955            editor.perform_format(
12956                project,
12957                FormatTrigger::Manual,
12958                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12959                window,
12960                cx,
12961            )
12962        })
12963        .unwrap();
12964    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12965    format.await;
12966    assert_eq!(
12967        editor.update(cx, |editor, cx| editor.text(cx)),
12968        "one\ntwo\nthree\n"
12969    );
12970}
12971
12972#[gpui::test]
12973async fn test_multiple_formatters(cx: &mut TestAppContext) {
12974    init_test(cx, |settings| {
12975        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12976        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12977            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12978            Formatter::CodeAction("code-action-1".into()),
12979            Formatter::CodeAction("code-action-2".into()),
12980        ]))
12981    });
12982
12983    let fs = FakeFs::new(cx.executor());
12984    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12985        .await;
12986
12987    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12988    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12989    language_registry.add(rust_lang());
12990
12991    let mut fake_servers = language_registry.register_fake_lsp(
12992        "Rust",
12993        FakeLspAdapter {
12994            capabilities: lsp::ServerCapabilities {
12995                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12996                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12997                    commands: vec!["the-command-for-code-action-1".into()],
12998                    ..Default::default()
12999                }),
13000                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
13001                ..Default::default()
13002            },
13003            ..Default::default()
13004        },
13005    );
13006
13007    let buffer = project
13008        .update(cx, |project, cx| {
13009            project.open_local_buffer(path!("/file.rs"), cx)
13010        })
13011        .await
13012        .unwrap();
13013
13014    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13015    let (editor, cx) = cx.add_window_view(|window, cx| {
13016        build_editor_with_project(project.clone(), buffer, window, cx)
13017    });
13018
13019    let fake_server = fake_servers.next().await.unwrap();
13020    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
13021        move |_params, _| async move {
13022            Ok(Some(vec![lsp::TextEdit::new(
13023                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13024                "applied-formatting\n".to_string(),
13025            )]))
13026        },
13027    );
13028    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13029        move |params, _| async move {
13030            let requested_code_actions = params.context.only.expect("Expected code action request");
13031            assert_eq!(requested_code_actions.len(), 1);
13032
13033            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
13034            let code_action = match requested_code_actions[0].as_str() {
13035                "code-action-1" => lsp::CodeAction {
13036                    kind: Some("code-action-1".into()),
13037                    edit: Some(lsp::WorkspaceEdit::new(
13038                        [(
13039                            uri,
13040                            vec![lsp::TextEdit::new(
13041                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13042                                "applied-code-action-1-edit\n".to_string(),
13043                            )],
13044                        )]
13045                        .into_iter()
13046                        .collect(),
13047                    )),
13048                    command: Some(lsp::Command {
13049                        command: "the-command-for-code-action-1".into(),
13050                        ..Default::default()
13051                    }),
13052                    ..Default::default()
13053                },
13054                "code-action-2" => lsp::CodeAction {
13055                    kind: Some("code-action-2".into()),
13056                    edit: Some(lsp::WorkspaceEdit::new(
13057                        [(
13058                            uri,
13059                            vec![lsp::TextEdit::new(
13060                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13061                                "applied-code-action-2-edit\n".to_string(),
13062                            )],
13063                        )]
13064                        .into_iter()
13065                        .collect(),
13066                    )),
13067                    ..Default::default()
13068                },
13069                req => panic!("Unexpected code action request: {:?}", req),
13070            };
13071            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13072                code_action,
13073            )]))
13074        },
13075    );
13076
13077    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
13078        move |params, _| async move { Ok(params) }
13079    });
13080
13081    let command_lock = Arc::new(futures::lock::Mutex::new(()));
13082    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
13083        let fake = fake_server.clone();
13084        let lock = command_lock.clone();
13085        move |params, _| {
13086            assert_eq!(params.command, "the-command-for-code-action-1");
13087            let fake = fake.clone();
13088            let lock = lock.clone();
13089            async move {
13090                lock.lock().await;
13091                fake.server
13092                    .request::<lsp::request::ApplyWorkspaceEdit>(
13093                        lsp::ApplyWorkspaceEditParams {
13094                            label: None,
13095                            edit: lsp::WorkspaceEdit {
13096                                changes: Some(
13097                                    [(
13098                                        lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
13099                                        vec![lsp::TextEdit {
13100                                            range: lsp::Range::new(
13101                                                lsp::Position::new(0, 0),
13102                                                lsp::Position::new(0, 0),
13103                                            ),
13104                                            new_text: "applied-code-action-1-command\n".into(),
13105                                        }],
13106                                    )]
13107                                    .into_iter()
13108                                    .collect(),
13109                                ),
13110                                ..Default::default()
13111                            },
13112                        },
13113                        DEFAULT_LSP_REQUEST_TIMEOUT,
13114                    )
13115                    .await
13116                    .into_response()
13117                    .unwrap();
13118                Ok(Some(json!(null)))
13119            }
13120        }
13121    });
13122
13123    editor
13124        .update_in(cx, |editor, window, cx| {
13125            editor.perform_format(
13126                project.clone(),
13127                FormatTrigger::Manual,
13128                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13129                window,
13130                cx,
13131            )
13132        })
13133        .unwrap()
13134        .await;
13135    editor.update(cx, |editor, cx| {
13136        assert_eq!(
13137            editor.text(cx),
13138            r#"
13139                applied-code-action-2-edit
13140                applied-code-action-1-command
13141                applied-code-action-1-edit
13142                applied-formatting
13143                one
13144                two
13145                three
13146            "#
13147            .unindent()
13148        );
13149    });
13150
13151    editor.update_in(cx, |editor, window, cx| {
13152        editor.undo(&Default::default(), window, cx);
13153        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
13154    });
13155
13156    // Perform a manual edit while waiting for an LSP command
13157    // that's being run as part of a formatting code action.
13158    let lock_guard = command_lock.lock().await;
13159    let format = editor
13160        .update_in(cx, |editor, window, cx| {
13161            editor.perform_format(
13162                project.clone(),
13163                FormatTrigger::Manual,
13164                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13165                window,
13166                cx,
13167            )
13168        })
13169        .unwrap();
13170    cx.run_until_parked();
13171    editor.update(cx, |editor, cx| {
13172        assert_eq!(
13173            editor.text(cx),
13174            r#"
13175                applied-code-action-1-edit
13176                applied-formatting
13177                one
13178                two
13179                three
13180            "#
13181            .unindent()
13182        );
13183
13184        editor.buffer.update(cx, |buffer, cx| {
13185            let ix = buffer.len(cx);
13186            buffer.edit([(ix..ix, "edited\n")], None, cx);
13187        });
13188    });
13189
13190    // Allow the LSP command to proceed. Because the buffer was edited,
13191    // the second code action will not be run.
13192    drop(lock_guard);
13193    format.await;
13194    editor.update_in(cx, |editor, window, cx| {
13195        assert_eq!(
13196            editor.text(cx),
13197            r#"
13198                applied-code-action-1-command
13199                applied-code-action-1-edit
13200                applied-formatting
13201                one
13202                two
13203                three
13204                edited
13205            "#
13206            .unindent()
13207        );
13208
13209        // The manual edit is undone first, because it is the last thing the user did
13210        // (even though the command completed afterwards).
13211        editor.undo(&Default::default(), window, cx);
13212        assert_eq!(
13213            editor.text(cx),
13214            r#"
13215                applied-code-action-1-command
13216                applied-code-action-1-edit
13217                applied-formatting
13218                one
13219                two
13220                three
13221            "#
13222            .unindent()
13223        );
13224
13225        // All the formatting (including the command, which completed after the manual edit)
13226        // is undone together.
13227        editor.undo(&Default::default(), window, cx);
13228        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
13229    });
13230}
13231
13232#[gpui::test]
13233async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
13234    init_test(cx, |settings| {
13235        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
13236            settings::LanguageServerFormatterSpecifier::Current,
13237        )]))
13238    });
13239
13240    let fs = FakeFs::new(cx.executor());
13241    fs.insert_file(path!("/file.ts"), Default::default()).await;
13242
13243    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13244
13245    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13246    language_registry.add(Arc::new(Language::new(
13247        LanguageConfig {
13248            name: "TypeScript".into(),
13249            matcher: LanguageMatcher {
13250                path_suffixes: vec!["ts".to_string()],
13251                ..Default::default()
13252            },
13253            ..LanguageConfig::default()
13254        },
13255        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13256    )));
13257    update_test_language_settings(cx, |settings| {
13258        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
13259    });
13260    let mut fake_servers = language_registry.register_fake_lsp(
13261        "TypeScript",
13262        FakeLspAdapter {
13263            capabilities: lsp::ServerCapabilities {
13264                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
13265                ..Default::default()
13266            },
13267            ..Default::default()
13268        },
13269    );
13270
13271    let buffer = project
13272        .update(cx, |project, cx| {
13273            project.open_local_buffer(path!("/file.ts"), cx)
13274        })
13275        .await
13276        .unwrap();
13277
13278    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13279    let (editor, cx) = cx.add_window_view(|window, cx| {
13280        build_editor_with_project(project.clone(), buffer, window, cx)
13281    });
13282    editor.update_in(cx, |editor, window, cx| {
13283        editor.set_text(
13284            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13285            window,
13286            cx,
13287        )
13288    });
13289
13290    let fake_server = fake_servers.next().await.unwrap();
13291
13292    let format = editor
13293        .update_in(cx, |editor, window, cx| {
13294            editor.perform_code_action_kind(
13295                project.clone(),
13296                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13297                window,
13298                cx,
13299            )
13300        })
13301        .unwrap();
13302    fake_server
13303        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
13304            assert_eq!(
13305                params.text_document.uri,
13306                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13307            );
13308            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13309                lsp::CodeAction {
13310                    title: "Organize Imports".to_string(),
13311                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
13312                    edit: Some(lsp::WorkspaceEdit {
13313                        changes: Some(
13314                            [(
13315                                params.text_document.uri.clone(),
13316                                vec![lsp::TextEdit::new(
13317                                    lsp::Range::new(
13318                                        lsp::Position::new(1, 0),
13319                                        lsp::Position::new(2, 0),
13320                                    ),
13321                                    "".to_string(),
13322                                )],
13323                            )]
13324                            .into_iter()
13325                            .collect(),
13326                        ),
13327                        ..Default::default()
13328                    }),
13329                    ..Default::default()
13330                },
13331            )]))
13332        })
13333        .next()
13334        .await;
13335    format.await;
13336    assert_eq!(
13337        editor.update(cx, |editor, cx| editor.text(cx)),
13338        "import { a } from 'module';\n\nconst x = a;\n"
13339    );
13340
13341    editor.update_in(cx, |editor, window, cx| {
13342        editor.set_text(
13343            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13344            window,
13345            cx,
13346        )
13347    });
13348    // Ensure we don't lock if code action hangs.
13349    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13350        move |params, _| async move {
13351            assert_eq!(
13352                params.text_document.uri,
13353                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13354            );
13355            futures::future::pending::<()>().await;
13356            unreachable!()
13357        },
13358    );
13359    let format = editor
13360        .update_in(cx, |editor, window, cx| {
13361            editor.perform_code_action_kind(
13362                project,
13363                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13364                window,
13365                cx,
13366            )
13367        })
13368        .unwrap();
13369    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
13370    format.await;
13371    assert_eq!(
13372        editor.update(cx, |editor, cx| editor.text(cx)),
13373        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
13374    );
13375}
13376
13377#[gpui::test]
13378async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
13379    init_test(cx, |_| {});
13380
13381    let mut cx = EditorLspTestContext::new_rust(
13382        lsp::ServerCapabilities {
13383            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13384            ..Default::default()
13385        },
13386        cx,
13387    )
13388    .await;
13389
13390    cx.set_state(indoc! {"
13391        one.twoˇ
13392    "});
13393
13394    // The format request takes a long time. When it completes, it inserts
13395    // a newline and an indent before the `.`
13396    cx.lsp
13397        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
13398            let executor = cx.background_executor().clone();
13399            async move {
13400                executor.timer(Duration::from_millis(100)).await;
13401                Ok(Some(vec![lsp::TextEdit {
13402                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
13403                    new_text: "\n    ".into(),
13404                }]))
13405            }
13406        });
13407
13408    // Submit a format request.
13409    let format_1 = cx
13410        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13411        .unwrap();
13412    cx.executor().run_until_parked();
13413
13414    // Submit a second format request.
13415    let format_2 = cx
13416        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13417        .unwrap();
13418    cx.executor().run_until_parked();
13419
13420    // Wait for both format requests to complete
13421    cx.executor().advance_clock(Duration::from_millis(200));
13422    format_1.await.unwrap();
13423    format_2.await.unwrap();
13424
13425    // The formatting edits only happens once.
13426    cx.assert_editor_state(indoc! {"
13427        one
13428            .twoˇ
13429    "});
13430}
13431
13432#[gpui::test]
13433async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
13434    init_test(cx, |settings| {
13435        settings.defaults.formatter = Some(FormatterList::default())
13436    });
13437
13438    let mut cx = EditorLspTestContext::new_rust(
13439        lsp::ServerCapabilities {
13440            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13441            ..Default::default()
13442        },
13443        cx,
13444    )
13445    .await;
13446
13447    // Record which buffer changes have been sent to the language server
13448    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
13449    cx.lsp
13450        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
13451            let buffer_changes = buffer_changes.clone();
13452            move |params, _| {
13453                buffer_changes.lock().extend(
13454                    params
13455                        .content_changes
13456                        .into_iter()
13457                        .map(|e| (e.range.unwrap(), e.text)),
13458                );
13459            }
13460        });
13461    // Handle formatting requests to the language server.
13462    cx.lsp
13463        .set_request_handler::<lsp::request::Formatting, _, _>({
13464            move |_, _| {
13465                // Insert blank lines between each line of the buffer.
13466                async move {
13467                    // TODO: this assertion is not reliably true. Currently nothing guarantees that we deliver
13468                    // DidChangedTextDocument to the LSP before sending the formatting request.
13469                    // assert_eq!(
13470                    //     &buffer_changes.lock()[1..],
13471                    //     &[
13472                    //         (
13473                    //             lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13474                    //             "".into()
13475                    //         ),
13476                    //         (
13477                    //             lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13478                    //             "".into()
13479                    //         ),
13480                    //         (
13481                    //             lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13482                    //             "\n".into()
13483                    //         ),
13484                    //     ]
13485                    // );
13486
13487                    Ok(Some(vec![
13488                        lsp::TextEdit {
13489                            range: lsp::Range::new(
13490                                lsp::Position::new(1, 0),
13491                                lsp::Position::new(1, 0),
13492                            ),
13493                            new_text: "\n".into(),
13494                        },
13495                        lsp::TextEdit {
13496                            range: lsp::Range::new(
13497                                lsp::Position::new(2, 0),
13498                                lsp::Position::new(2, 0),
13499                            ),
13500                            new_text: "\n".into(),
13501                        },
13502                    ]))
13503                }
13504            }
13505        });
13506
13507    // Set up a buffer white some trailing whitespace and no trailing newline.
13508    cx.set_state(
13509        &[
13510            "one ",   //
13511            "twoˇ",   //
13512            "three ", //
13513            "four",   //
13514        ]
13515        .join("\n"),
13516    );
13517
13518    // Submit a format request.
13519    let format = cx
13520        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13521        .unwrap();
13522
13523    cx.run_until_parked();
13524    // After formatting the buffer, the trailing whitespace is stripped,
13525    // a newline is appended, and the edits provided by the language server
13526    // have been applied.
13527    format.await.unwrap();
13528
13529    cx.assert_editor_state(
13530        &[
13531            "one",   //
13532            "",      //
13533            "twoˇ",  //
13534            "",      //
13535            "three", //
13536            "four",  //
13537            "",      //
13538        ]
13539        .join("\n"),
13540    );
13541
13542    // Undoing the formatting undoes the trailing whitespace removal, the
13543    // trailing newline, and the LSP edits.
13544    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13545    cx.assert_editor_state(
13546        &[
13547            "one ",   //
13548            "twoˇ",   //
13549            "three ", //
13550            "four",   //
13551        ]
13552        .join("\n"),
13553    );
13554}
13555
13556#[gpui::test]
13557async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13558    cx: &mut TestAppContext,
13559) {
13560    init_test(cx, |_| {});
13561
13562    cx.update(|cx| {
13563        cx.update_global::<SettingsStore, _>(|settings, cx| {
13564            settings.update_user_settings(cx, |settings| {
13565                settings.editor.auto_signature_help = Some(true);
13566                settings.editor.hover_popover_delay = Some(DelayMs(300));
13567            });
13568        });
13569    });
13570
13571    let mut cx = EditorLspTestContext::new_rust(
13572        lsp::ServerCapabilities {
13573            signature_help_provider: Some(lsp::SignatureHelpOptions {
13574                ..Default::default()
13575            }),
13576            ..Default::default()
13577        },
13578        cx,
13579    )
13580    .await;
13581
13582    let language = Language::new(
13583        LanguageConfig {
13584            name: "Rust".into(),
13585            brackets: BracketPairConfig {
13586                pairs: vec![
13587                    BracketPair {
13588                        start: "{".to_string(),
13589                        end: "}".to_string(),
13590                        close: true,
13591                        surround: true,
13592                        newline: true,
13593                    },
13594                    BracketPair {
13595                        start: "(".to_string(),
13596                        end: ")".to_string(),
13597                        close: true,
13598                        surround: true,
13599                        newline: true,
13600                    },
13601                    BracketPair {
13602                        start: "/*".to_string(),
13603                        end: " */".to_string(),
13604                        close: true,
13605                        surround: true,
13606                        newline: true,
13607                    },
13608                    BracketPair {
13609                        start: "[".to_string(),
13610                        end: "]".to_string(),
13611                        close: false,
13612                        surround: false,
13613                        newline: true,
13614                    },
13615                    BracketPair {
13616                        start: "\"".to_string(),
13617                        end: "\"".to_string(),
13618                        close: true,
13619                        surround: true,
13620                        newline: false,
13621                    },
13622                    BracketPair {
13623                        start: "<".to_string(),
13624                        end: ">".to_string(),
13625                        close: false,
13626                        surround: true,
13627                        newline: true,
13628                    },
13629                ],
13630                ..Default::default()
13631            },
13632            autoclose_before: "})]".to_string(),
13633            ..Default::default()
13634        },
13635        Some(tree_sitter_rust::LANGUAGE.into()),
13636    );
13637    let language = Arc::new(language);
13638
13639    cx.language_registry().add(language.clone());
13640    cx.update_buffer(|buffer, cx| {
13641        buffer.set_language(Some(language), cx);
13642    });
13643
13644    cx.set_state(
13645        &r#"
13646            fn main() {
13647                sampleˇ
13648            }
13649        "#
13650        .unindent(),
13651    );
13652
13653    cx.update_editor(|editor, window, cx| {
13654        editor.handle_input("(", window, cx);
13655    });
13656    cx.assert_editor_state(
13657        &"
13658            fn main() {
13659                sample(ˇ)
13660            }
13661        "
13662        .unindent(),
13663    );
13664
13665    let mocked_response = lsp::SignatureHelp {
13666        signatures: vec![lsp::SignatureInformation {
13667            label: "fn sample(param1: u8, param2: u8)".to_string(),
13668            documentation: None,
13669            parameters: Some(vec![
13670                lsp::ParameterInformation {
13671                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13672                    documentation: None,
13673                },
13674                lsp::ParameterInformation {
13675                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13676                    documentation: None,
13677                },
13678            ]),
13679            active_parameter: None,
13680        }],
13681        active_signature: Some(0),
13682        active_parameter: Some(0),
13683    };
13684    handle_signature_help_request(&mut cx, mocked_response).await;
13685
13686    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13687        .await;
13688
13689    cx.editor(|editor, _, _| {
13690        let signature_help_state = editor.signature_help_state.popover().cloned();
13691        let signature = signature_help_state.unwrap();
13692        assert_eq!(
13693            signature.signatures[signature.current_signature].label,
13694            "fn sample(param1: u8, param2: u8)"
13695        );
13696    });
13697}
13698
13699#[gpui::test]
13700async fn test_signature_help_delay_only_for_auto(cx: &mut TestAppContext) {
13701    init_test(cx, |_| {});
13702
13703    let delay_ms = 500;
13704    cx.update(|cx| {
13705        cx.update_global::<SettingsStore, _>(|settings, cx| {
13706            settings.update_user_settings(cx, |settings| {
13707                settings.editor.auto_signature_help = Some(true);
13708                settings.editor.show_signature_help_after_edits = Some(false);
13709                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
13710            });
13711        });
13712    });
13713
13714    let mut cx = EditorLspTestContext::new_rust(
13715        lsp::ServerCapabilities {
13716            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13717            ..lsp::ServerCapabilities::default()
13718        },
13719        cx,
13720    )
13721    .await;
13722
13723    let mocked_response = lsp::SignatureHelp {
13724        signatures: vec![lsp::SignatureInformation {
13725            label: "fn sample(param1: u8)".to_string(),
13726            documentation: None,
13727            parameters: Some(vec![lsp::ParameterInformation {
13728                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13729                documentation: None,
13730            }]),
13731            active_parameter: None,
13732        }],
13733        active_signature: Some(0),
13734        active_parameter: Some(0),
13735    };
13736
13737    cx.set_state(indoc! {"
13738        fn main() {
13739            sample(ˇ);
13740        }
13741
13742        fn sample(param1: u8) {}
13743    "});
13744
13745    // Manual trigger should show immediately without delay
13746    cx.update_editor(|editor, window, cx| {
13747        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13748    });
13749    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13750    cx.run_until_parked();
13751    cx.editor(|editor, _, _| {
13752        assert!(
13753            editor.signature_help_state.is_shown(),
13754            "Manual trigger should show signature help without delay"
13755        );
13756    });
13757
13758    cx.update_editor(|editor, _, cx| {
13759        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13760    });
13761    cx.run_until_parked();
13762    cx.editor(|editor, _, _| {
13763        assert!(!editor.signature_help_state.is_shown());
13764    });
13765
13766    // Auto trigger (cursor movement into brackets) should respect delay
13767    cx.set_state(indoc! {"
13768        fn main() {
13769            sampleˇ();
13770        }
13771
13772        fn sample(param1: u8) {}
13773    "});
13774    cx.update_editor(|editor, window, cx| {
13775        editor.move_right(&MoveRight, window, cx);
13776    });
13777    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13778    cx.run_until_parked();
13779    cx.editor(|editor, _, _| {
13780        assert!(
13781            !editor.signature_help_state.is_shown(),
13782            "Auto trigger should wait for delay before showing signature help"
13783        );
13784    });
13785
13786    cx.executor()
13787        .advance_clock(Duration::from_millis(delay_ms + 50));
13788    cx.run_until_parked();
13789    cx.editor(|editor, _, _| {
13790        assert!(
13791            editor.signature_help_state.is_shown(),
13792            "Auto trigger should show signature help after delay elapsed"
13793        );
13794    });
13795}
13796
13797#[gpui::test]
13798async fn test_signature_help_after_edits_no_delay(cx: &mut TestAppContext) {
13799    init_test(cx, |_| {});
13800
13801    let delay_ms = 500;
13802    cx.update(|cx| {
13803        cx.update_global::<SettingsStore, _>(|settings, cx| {
13804            settings.update_user_settings(cx, |settings| {
13805                settings.editor.auto_signature_help = Some(false);
13806                settings.editor.show_signature_help_after_edits = Some(true);
13807                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
13808            });
13809        });
13810    });
13811
13812    let mut cx = EditorLspTestContext::new_rust(
13813        lsp::ServerCapabilities {
13814            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13815            ..lsp::ServerCapabilities::default()
13816        },
13817        cx,
13818    )
13819    .await;
13820
13821    let language = Arc::new(Language::new(
13822        LanguageConfig {
13823            name: "Rust".into(),
13824            brackets: BracketPairConfig {
13825                pairs: vec![BracketPair {
13826                    start: "(".to_string(),
13827                    end: ")".to_string(),
13828                    close: true,
13829                    surround: true,
13830                    newline: true,
13831                }],
13832                ..BracketPairConfig::default()
13833            },
13834            autoclose_before: "})".to_string(),
13835            ..LanguageConfig::default()
13836        },
13837        Some(tree_sitter_rust::LANGUAGE.into()),
13838    ));
13839    cx.language_registry().add(language.clone());
13840    cx.update_buffer(|buffer, cx| {
13841        buffer.set_language(Some(language), cx);
13842    });
13843
13844    let mocked_response = lsp::SignatureHelp {
13845        signatures: vec![lsp::SignatureInformation {
13846            label: "fn sample(param1: u8)".to_string(),
13847            documentation: None,
13848            parameters: Some(vec![lsp::ParameterInformation {
13849                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13850                documentation: None,
13851            }]),
13852            active_parameter: None,
13853        }],
13854        active_signature: Some(0),
13855        active_parameter: Some(0),
13856    };
13857
13858    cx.set_state(indoc! {"
13859        fn main() {
13860            sampleˇ
13861        }
13862    "});
13863
13864    // Typing bracket should show signature help immediately without delay
13865    cx.update_editor(|editor, window, cx| {
13866        editor.handle_input("(", window, cx);
13867    });
13868    handle_signature_help_request(&mut cx, mocked_response).await;
13869    cx.run_until_parked();
13870    cx.editor(|editor, _, _| {
13871        assert!(
13872            editor.signature_help_state.is_shown(),
13873            "show_signature_help_after_edits should show signature help without delay"
13874        );
13875    });
13876}
13877
13878#[gpui::test]
13879async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13880    init_test(cx, |_| {});
13881
13882    cx.update(|cx| {
13883        cx.update_global::<SettingsStore, _>(|settings, cx| {
13884            settings.update_user_settings(cx, |settings| {
13885                settings.editor.auto_signature_help = Some(false);
13886                settings.editor.show_signature_help_after_edits = Some(false);
13887            });
13888        });
13889    });
13890
13891    let mut cx = EditorLspTestContext::new_rust(
13892        lsp::ServerCapabilities {
13893            signature_help_provider: Some(lsp::SignatureHelpOptions {
13894                ..Default::default()
13895            }),
13896            ..Default::default()
13897        },
13898        cx,
13899    )
13900    .await;
13901
13902    let language = Language::new(
13903        LanguageConfig {
13904            name: "Rust".into(),
13905            brackets: BracketPairConfig {
13906                pairs: vec![
13907                    BracketPair {
13908                        start: "{".to_string(),
13909                        end: "}".to_string(),
13910                        close: true,
13911                        surround: true,
13912                        newline: true,
13913                    },
13914                    BracketPair {
13915                        start: "(".to_string(),
13916                        end: ")".to_string(),
13917                        close: true,
13918                        surround: true,
13919                        newline: true,
13920                    },
13921                    BracketPair {
13922                        start: "/*".to_string(),
13923                        end: " */".to_string(),
13924                        close: true,
13925                        surround: true,
13926                        newline: true,
13927                    },
13928                    BracketPair {
13929                        start: "[".to_string(),
13930                        end: "]".to_string(),
13931                        close: false,
13932                        surround: false,
13933                        newline: true,
13934                    },
13935                    BracketPair {
13936                        start: "\"".to_string(),
13937                        end: "\"".to_string(),
13938                        close: true,
13939                        surround: true,
13940                        newline: false,
13941                    },
13942                    BracketPair {
13943                        start: "<".to_string(),
13944                        end: ">".to_string(),
13945                        close: false,
13946                        surround: true,
13947                        newline: true,
13948                    },
13949                ],
13950                ..Default::default()
13951            },
13952            autoclose_before: "})]".to_string(),
13953            ..Default::default()
13954        },
13955        Some(tree_sitter_rust::LANGUAGE.into()),
13956    );
13957    let language = Arc::new(language);
13958
13959    cx.language_registry().add(language.clone());
13960    cx.update_buffer(|buffer, cx| {
13961        buffer.set_language(Some(language), cx);
13962    });
13963
13964    // Ensure that signature_help is not called when no signature help is enabled.
13965    cx.set_state(
13966        &r#"
13967            fn main() {
13968                sampleˇ
13969            }
13970        "#
13971        .unindent(),
13972    );
13973    cx.update_editor(|editor, window, cx| {
13974        editor.handle_input("(", window, cx);
13975    });
13976    cx.assert_editor_state(
13977        &"
13978            fn main() {
13979                sample(ˇ)
13980            }
13981        "
13982        .unindent(),
13983    );
13984    cx.editor(|editor, _, _| {
13985        assert!(editor.signature_help_state.task().is_none());
13986    });
13987
13988    let mocked_response = lsp::SignatureHelp {
13989        signatures: vec![lsp::SignatureInformation {
13990            label: "fn sample(param1: u8, param2: u8)".to_string(),
13991            documentation: None,
13992            parameters: Some(vec![
13993                lsp::ParameterInformation {
13994                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13995                    documentation: None,
13996                },
13997                lsp::ParameterInformation {
13998                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13999                    documentation: None,
14000                },
14001            ]),
14002            active_parameter: None,
14003        }],
14004        active_signature: Some(0),
14005        active_parameter: Some(0),
14006    };
14007
14008    // Ensure that signature_help is called when enabled afte edits
14009    cx.update(|_, cx| {
14010        cx.update_global::<SettingsStore, _>(|settings, cx| {
14011            settings.update_user_settings(cx, |settings| {
14012                settings.editor.auto_signature_help = Some(false);
14013                settings.editor.show_signature_help_after_edits = Some(true);
14014            });
14015        });
14016    });
14017    cx.set_state(
14018        &r#"
14019            fn main() {
14020                sampleˇ
14021            }
14022        "#
14023        .unindent(),
14024    );
14025    cx.update_editor(|editor, window, cx| {
14026        editor.handle_input("(", window, cx);
14027    });
14028    cx.assert_editor_state(
14029        &"
14030            fn main() {
14031                sample(ˇ)
14032            }
14033        "
14034        .unindent(),
14035    );
14036    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14037    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14038        .await;
14039    cx.update_editor(|editor, _, _| {
14040        let signature_help_state = editor.signature_help_state.popover().cloned();
14041        assert!(signature_help_state.is_some());
14042        let signature = signature_help_state.unwrap();
14043        assert_eq!(
14044            signature.signatures[signature.current_signature].label,
14045            "fn sample(param1: u8, param2: u8)"
14046        );
14047        editor.signature_help_state = SignatureHelpState::default();
14048    });
14049
14050    // Ensure that signature_help is called when auto signature help override is enabled
14051    cx.update(|_, cx| {
14052        cx.update_global::<SettingsStore, _>(|settings, cx| {
14053            settings.update_user_settings(cx, |settings| {
14054                settings.editor.auto_signature_help = Some(true);
14055                settings.editor.show_signature_help_after_edits = Some(false);
14056            });
14057        });
14058    });
14059    cx.set_state(
14060        &r#"
14061            fn main() {
14062                sampleˇ
14063            }
14064        "#
14065        .unindent(),
14066    );
14067    cx.update_editor(|editor, window, cx| {
14068        editor.handle_input("(", window, cx);
14069    });
14070    cx.assert_editor_state(
14071        &"
14072            fn main() {
14073                sample(ˇ)
14074            }
14075        "
14076        .unindent(),
14077    );
14078    handle_signature_help_request(&mut cx, mocked_response).await;
14079    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14080        .await;
14081    cx.editor(|editor, _, _| {
14082        let signature_help_state = editor.signature_help_state.popover().cloned();
14083        assert!(signature_help_state.is_some());
14084        let signature = signature_help_state.unwrap();
14085        assert_eq!(
14086            signature.signatures[signature.current_signature].label,
14087            "fn sample(param1: u8, param2: u8)"
14088        );
14089    });
14090}
14091
14092#[gpui::test]
14093async fn test_signature_help(cx: &mut TestAppContext) {
14094    init_test(cx, |_| {});
14095    cx.update(|cx| {
14096        cx.update_global::<SettingsStore, _>(|settings, cx| {
14097            settings.update_user_settings(cx, |settings| {
14098                settings.editor.auto_signature_help = Some(true);
14099            });
14100        });
14101    });
14102
14103    let mut cx = EditorLspTestContext::new_rust(
14104        lsp::ServerCapabilities {
14105            signature_help_provider: Some(lsp::SignatureHelpOptions {
14106                ..Default::default()
14107            }),
14108            ..Default::default()
14109        },
14110        cx,
14111    )
14112    .await;
14113
14114    // A test that directly calls `show_signature_help`
14115    cx.update_editor(|editor, window, cx| {
14116        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14117    });
14118
14119    let mocked_response = lsp::SignatureHelp {
14120        signatures: vec![lsp::SignatureInformation {
14121            label: "fn sample(param1: u8, param2: u8)".to_string(),
14122            documentation: None,
14123            parameters: Some(vec![
14124                lsp::ParameterInformation {
14125                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14126                    documentation: None,
14127                },
14128                lsp::ParameterInformation {
14129                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14130                    documentation: None,
14131                },
14132            ]),
14133            active_parameter: None,
14134        }],
14135        active_signature: Some(0),
14136        active_parameter: Some(0),
14137    };
14138    handle_signature_help_request(&mut cx, mocked_response).await;
14139
14140    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14141        .await;
14142
14143    cx.editor(|editor, _, _| {
14144        let signature_help_state = editor.signature_help_state.popover().cloned();
14145        assert!(signature_help_state.is_some());
14146        let signature = signature_help_state.unwrap();
14147        assert_eq!(
14148            signature.signatures[signature.current_signature].label,
14149            "fn sample(param1: u8, param2: u8)"
14150        );
14151    });
14152
14153    // When exiting outside from inside the brackets, `signature_help` is closed.
14154    cx.set_state(indoc! {"
14155        fn main() {
14156            sample(ˇ);
14157        }
14158
14159        fn sample(param1: u8, param2: u8) {}
14160    "});
14161
14162    cx.update_editor(|editor, window, cx| {
14163        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14164            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
14165        });
14166    });
14167
14168    let mocked_response = lsp::SignatureHelp {
14169        signatures: Vec::new(),
14170        active_signature: None,
14171        active_parameter: None,
14172    };
14173    handle_signature_help_request(&mut cx, mocked_response).await;
14174
14175    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
14176        .await;
14177
14178    cx.editor(|editor, _, _| {
14179        assert!(!editor.signature_help_state.is_shown());
14180    });
14181
14182    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
14183    cx.set_state(indoc! {"
14184        fn main() {
14185            sample(ˇ);
14186        }
14187
14188        fn sample(param1: u8, param2: u8) {}
14189    "});
14190
14191    let mocked_response = lsp::SignatureHelp {
14192        signatures: vec![lsp::SignatureInformation {
14193            label: "fn sample(param1: u8, param2: u8)".to_string(),
14194            documentation: None,
14195            parameters: Some(vec![
14196                lsp::ParameterInformation {
14197                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14198                    documentation: None,
14199                },
14200                lsp::ParameterInformation {
14201                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14202                    documentation: None,
14203                },
14204            ]),
14205            active_parameter: None,
14206        }],
14207        active_signature: Some(0),
14208        active_parameter: Some(0),
14209    };
14210    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14211    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14212        .await;
14213    cx.editor(|editor, _, _| {
14214        assert!(editor.signature_help_state.is_shown());
14215    });
14216
14217    // Restore the popover with more parameter input
14218    cx.set_state(indoc! {"
14219        fn main() {
14220            sample(param1, param2ˇ);
14221        }
14222
14223        fn sample(param1: u8, param2: u8) {}
14224    "});
14225
14226    let mocked_response = lsp::SignatureHelp {
14227        signatures: vec![lsp::SignatureInformation {
14228            label: "fn sample(param1: u8, param2: u8)".to_string(),
14229            documentation: None,
14230            parameters: Some(vec![
14231                lsp::ParameterInformation {
14232                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14233                    documentation: None,
14234                },
14235                lsp::ParameterInformation {
14236                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14237                    documentation: None,
14238                },
14239            ]),
14240            active_parameter: None,
14241        }],
14242        active_signature: Some(0),
14243        active_parameter: Some(1),
14244    };
14245    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14246    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14247        .await;
14248
14249    // When selecting a range, the popover is gone.
14250    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
14251    cx.update_editor(|editor, window, cx| {
14252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14253            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
14254        })
14255    });
14256    cx.assert_editor_state(indoc! {"
14257        fn main() {
14258            sample(param1, «ˇparam2»);
14259        }
14260
14261        fn sample(param1: u8, param2: u8) {}
14262    "});
14263    cx.editor(|editor, _, _| {
14264        assert!(!editor.signature_help_state.is_shown());
14265    });
14266
14267    // When unselecting again, the popover is back if within the brackets.
14268    cx.update_editor(|editor, window, cx| {
14269        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14270            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14271        })
14272    });
14273    cx.assert_editor_state(indoc! {"
14274        fn main() {
14275            sample(param1, ˇparam2);
14276        }
14277
14278        fn sample(param1: u8, param2: u8) {}
14279    "});
14280    handle_signature_help_request(&mut cx, mocked_response).await;
14281    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14282        .await;
14283    cx.editor(|editor, _, _| {
14284        assert!(editor.signature_help_state.is_shown());
14285    });
14286
14287    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
14288    cx.update_editor(|editor, window, cx| {
14289        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14290            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
14291            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14292        })
14293    });
14294    cx.assert_editor_state(indoc! {"
14295        fn main() {
14296            sample(param1, ˇparam2);
14297        }
14298
14299        fn sample(param1: u8, param2: u8) {}
14300    "});
14301
14302    let mocked_response = lsp::SignatureHelp {
14303        signatures: vec![lsp::SignatureInformation {
14304            label: "fn sample(param1: u8, param2: u8)".to_string(),
14305            documentation: None,
14306            parameters: Some(vec![
14307                lsp::ParameterInformation {
14308                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14309                    documentation: None,
14310                },
14311                lsp::ParameterInformation {
14312                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14313                    documentation: None,
14314                },
14315            ]),
14316            active_parameter: None,
14317        }],
14318        active_signature: Some(0),
14319        active_parameter: Some(1),
14320    };
14321    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14322    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14323        .await;
14324    cx.update_editor(|editor, _, cx| {
14325        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
14326    });
14327    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
14328        .await;
14329    cx.update_editor(|editor, window, cx| {
14330        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14331            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
14332        })
14333    });
14334    cx.assert_editor_state(indoc! {"
14335        fn main() {
14336            sample(param1, «ˇparam2»);
14337        }
14338
14339        fn sample(param1: u8, param2: u8) {}
14340    "});
14341    cx.update_editor(|editor, window, cx| {
14342        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14343            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14344        })
14345    });
14346    cx.assert_editor_state(indoc! {"
14347        fn main() {
14348            sample(param1, ˇparam2);
14349        }
14350
14351        fn sample(param1: u8, param2: u8) {}
14352    "});
14353    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
14354        .await;
14355}
14356
14357#[gpui::test]
14358async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
14359    init_test(cx, |_| {});
14360
14361    let mut cx = EditorLspTestContext::new_rust(
14362        lsp::ServerCapabilities {
14363            signature_help_provider: Some(lsp::SignatureHelpOptions {
14364                ..Default::default()
14365            }),
14366            ..Default::default()
14367        },
14368        cx,
14369    )
14370    .await;
14371
14372    cx.set_state(indoc! {"
14373        fn main() {
14374            overloadedˇ
14375        }
14376    "});
14377
14378    cx.update_editor(|editor, window, cx| {
14379        editor.handle_input("(", window, cx);
14380        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14381    });
14382
14383    // Mock response with 3 signatures
14384    let mocked_response = lsp::SignatureHelp {
14385        signatures: vec![
14386            lsp::SignatureInformation {
14387                label: "fn overloaded(x: i32)".to_string(),
14388                documentation: None,
14389                parameters: Some(vec![lsp::ParameterInformation {
14390                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14391                    documentation: None,
14392                }]),
14393                active_parameter: None,
14394            },
14395            lsp::SignatureInformation {
14396                label: "fn overloaded(x: i32, y: i32)".to_string(),
14397                documentation: None,
14398                parameters: Some(vec![
14399                    lsp::ParameterInformation {
14400                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14401                        documentation: None,
14402                    },
14403                    lsp::ParameterInformation {
14404                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14405                        documentation: None,
14406                    },
14407                ]),
14408                active_parameter: None,
14409            },
14410            lsp::SignatureInformation {
14411                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
14412                documentation: None,
14413                parameters: Some(vec![
14414                    lsp::ParameterInformation {
14415                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14416                        documentation: None,
14417                    },
14418                    lsp::ParameterInformation {
14419                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14420                        documentation: None,
14421                    },
14422                    lsp::ParameterInformation {
14423                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
14424                        documentation: None,
14425                    },
14426                ]),
14427                active_parameter: None,
14428            },
14429        ],
14430        active_signature: Some(1),
14431        active_parameter: Some(0),
14432    };
14433    handle_signature_help_request(&mut cx, mocked_response).await;
14434
14435    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14436        .await;
14437
14438    // Verify we have multiple signatures and the right one is selected
14439    cx.editor(|editor, _, _| {
14440        let popover = editor.signature_help_state.popover().cloned().unwrap();
14441        assert_eq!(popover.signatures.len(), 3);
14442        // active_signature was 1, so that should be the current
14443        assert_eq!(popover.current_signature, 1);
14444        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
14445        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
14446        assert_eq!(
14447            popover.signatures[2].label,
14448            "fn overloaded(x: i32, y: i32, z: i32)"
14449        );
14450    });
14451
14452    // Test navigation functionality
14453    cx.update_editor(|editor, window, cx| {
14454        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14455    });
14456
14457    cx.editor(|editor, _, _| {
14458        let popover = editor.signature_help_state.popover().cloned().unwrap();
14459        assert_eq!(popover.current_signature, 2);
14460    });
14461
14462    // Test wrap around
14463    cx.update_editor(|editor, window, cx| {
14464        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14465    });
14466
14467    cx.editor(|editor, _, _| {
14468        let popover = editor.signature_help_state.popover().cloned().unwrap();
14469        assert_eq!(popover.current_signature, 0);
14470    });
14471
14472    // Test previous navigation
14473    cx.update_editor(|editor, window, cx| {
14474        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
14475    });
14476
14477    cx.editor(|editor, _, _| {
14478        let popover = editor.signature_help_state.popover().cloned().unwrap();
14479        assert_eq!(popover.current_signature, 2);
14480    });
14481}
14482
14483#[gpui::test]
14484async fn test_completion_mode(cx: &mut TestAppContext) {
14485    init_test(cx, |_| {});
14486    let mut cx = EditorLspTestContext::new_rust(
14487        lsp::ServerCapabilities {
14488            completion_provider: Some(lsp::CompletionOptions {
14489                resolve_provider: Some(true),
14490                ..Default::default()
14491            }),
14492            ..Default::default()
14493        },
14494        cx,
14495    )
14496    .await;
14497
14498    struct Run {
14499        run_description: &'static str,
14500        initial_state: String,
14501        buffer_marked_text: String,
14502        completion_label: &'static str,
14503        completion_text: &'static str,
14504        expected_with_insert_mode: String,
14505        expected_with_replace_mode: String,
14506        expected_with_replace_subsequence_mode: String,
14507        expected_with_replace_suffix_mode: String,
14508    }
14509
14510    let runs = [
14511        Run {
14512            run_description: "Start of word matches completion text",
14513            initial_state: "before ediˇ after".into(),
14514            buffer_marked_text: "before <edi|> after".into(),
14515            completion_label: "editor",
14516            completion_text: "editor",
14517            expected_with_insert_mode: "before editorˇ after".into(),
14518            expected_with_replace_mode: "before editorˇ after".into(),
14519            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14520            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14521        },
14522        Run {
14523            run_description: "Accept same text at the middle of the word",
14524            initial_state: "before ediˇtor after".into(),
14525            buffer_marked_text: "before <edi|tor> after".into(),
14526            completion_label: "editor",
14527            completion_text: "editor",
14528            expected_with_insert_mode: "before editorˇtor after".into(),
14529            expected_with_replace_mode: "before editorˇ after".into(),
14530            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14531            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14532        },
14533        Run {
14534            run_description: "End of word matches completion text -- cursor at end",
14535            initial_state: "before torˇ after".into(),
14536            buffer_marked_text: "before <tor|> after".into(),
14537            completion_label: "editor",
14538            completion_text: "editor",
14539            expected_with_insert_mode: "before editorˇ after".into(),
14540            expected_with_replace_mode: "before editorˇ after".into(),
14541            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14542            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14543        },
14544        Run {
14545            run_description: "End of word matches completion text -- cursor at start",
14546            initial_state: "before ˇtor after".into(),
14547            buffer_marked_text: "before <|tor> after".into(),
14548            completion_label: "editor",
14549            completion_text: "editor",
14550            expected_with_insert_mode: "before editorˇtor after".into(),
14551            expected_with_replace_mode: "before editorˇ after".into(),
14552            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14553            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14554        },
14555        Run {
14556            run_description: "Prepend text containing whitespace",
14557            initial_state: "pˇfield: bool".into(),
14558            buffer_marked_text: "<p|field>: bool".into(),
14559            completion_label: "pub ",
14560            completion_text: "pub ",
14561            expected_with_insert_mode: "pub ˇfield: bool".into(),
14562            expected_with_replace_mode: "pub ˇ: bool".into(),
14563            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
14564            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
14565        },
14566        Run {
14567            run_description: "Add element to start of list",
14568            initial_state: "[element_ˇelement_2]".into(),
14569            buffer_marked_text: "[<element_|element_2>]".into(),
14570            completion_label: "element_1",
14571            completion_text: "element_1",
14572            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
14573            expected_with_replace_mode: "[element_1ˇ]".into(),
14574            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
14575            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
14576        },
14577        Run {
14578            run_description: "Add element to start of list -- first and second elements are equal",
14579            initial_state: "[elˇelement]".into(),
14580            buffer_marked_text: "[<el|element>]".into(),
14581            completion_label: "element",
14582            completion_text: "element",
14583            expected_with_insert_mode: "[elementˇelement]".into(),
14584            expected_with_replace_mode: "[elementˇ]".into(),
14585            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
14586            expected_with_replace_suffix_mode: "[elementˇ]".into(),
14587        },
14588        Run {
14589            run_description: "Ends with matching suffix",
14590            initial_state: "SubˇError".into(),
14591            buffer_marked_text: "<Sub|Error>".into(),
14592            completion_label: "SubscriptionError",
14593            completion_text: "SubscriptionError",
14594            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
14595            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14596            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14597            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
14598        },
14599        Run {
14600            run_description: "Suffix is a subsequence -- contiguous",
14601            initial_state: "SubˇErr".into(),
14602            buffer_marked_text: "<Sub|Err>".into(),
14603            completion_label: "SubscriptionError",
14604            completion_text: "SubscriptionError",
14605            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
14606            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14607            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14608            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
14609        },
14610        Run {
14611            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
14612            initial_state: "Suˇscrirr".into(),
14613            buffer_marked_text: "<Su|scrirr>".into(),
14614            completion_label: "SubscriptionError",
14615            completion_text: "SubscriptionError",
14616            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
14617            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14618            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14619            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
14620        },
14621        Run {
14622            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
14623            initial_state: "foo(indˇix)".into(),
14624            buffer_marked_text: "foo(<ind|ix>)".into(),
14625            completion_label: "node_index",
14626            completion_text: "node_index",
14627            expected_with_insert_mode: "foo(node_indexˇix)".into(),
14628            expected_with_replace_mode: "foo(node_indexˇ)".into(),
14629            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
14630            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
14631        },
14632        Run {
14633            run_description: "Replace range ends before cursor - should extend to cursor",
14634            initial_state: "before editˇo after".into(),
14635            buffer_marked_text: "before <{ed}>it|o after".into(),
14636            completion_label: "editor",
14637            completion_text: "editor",
14638            expected_with_insert_mode: "before editorˇo after".into(),
14639            expected_with_replace_mode: "before editorˇo after".into(),
14640            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14641            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14642        },
14643        Run {
14644            run_description: "Uses label for suffix matching",
14645            initial_state: "before ediˇtor after".into(),
14646            buffer_marked_text: "before <edi|tor> after".into(),
14647            completion_label: "editor",
14648            completion_text: "editor()",
14649            expected_with_insert_mode: "before editor()ˇtor after".into(),
14650            expected_with_replace_mode: "before editor()ˇ after".into(),
14651            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14652            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14653        },
14654        Run {
14655            run_description: "Case insensitive subsequence and suffix matching",
14656            initial_state: "before EDiˇtoR after".into(),
14657            buffer_marked_text: "before <EDi|toR> after".into(),
14658            completion_label: "editor",
14659            completion_text: "editor",
14660            expected_with_insert_mode: "before editorˇtoR after".into(),
14661            expected_with_replace_mode: "before editorˇ after".into(),
14662            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14663            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14664        },
14665    ];
14666
14667    for run in runs {
14668        let run_variations = [
14669            (LspInsertMode::Insert, run.expected_with_insert_mode),
14670            (LspInsertMode::Replace, run.expected_with_replace_mode),
14671            (
14672                LspInsertMode::ReplaceSubsequence,
14673                run.expected_with_replace_subsequence_mode,
14674            ),
14675            (
14676                LspInsertMode::ReplaceSuffix,
14677                run.expected_with_replace_suffix_mode,
14678            ),
14679        ];
14680
14681        for (lsp_insert_mode, expected_text) in run_variations {
14682            eprintln!(
14683                "run = {:?}, mode = {lsp_insert_mode:.?}",
14684                run.run_description,
14685            );
14686
14687            update_test_language_settings(&mut cx, |settings| {
14688                settings.defaults.completions = Some(CompletionSettingsContent {
14689                    lsp_insert_mode: Some(lsp_insert_mode),
14690                    words: Some(WordsCompletionMode::Disabled),
14691                    words_min_length: Some(0),
14692                    ..Default::default()
14693                });
14694            });
14695
14696            cx.set_state(&run.initial_state);
14697
14698            // Set up resolve handler before showing completions, since resolve may be
14699            // triggered when menu becomes visible (for documentation), not just on confirm.
14700            cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
14701                move |_, _, _| async move {
14702                    Ok(lsp::CompletionItem {
14703                        additional_text_edits: None,
14704                        ..Default::default()
14705                    })
14706                },
14707            );
14708
14709            cx.update_editor(|editor, window, cx| {
14710                editor.show_completions(&ShowCompletions, window, cx);
14711            });
14712
14713            let counter = Arc::new(AtomicUsize::new(0));
14714            handle_completion_request_with_insert_and_replace(
14715                &mut cx,
14716                &run.buffer_marked_text,
14717                vec![(run.completion_label, run.completion_text)],
14718                counter.clone(),
14719            )
14720            .await;
14721            cx.condition(|editor, _| editor.context_menu_visible())
14722                .await;
14723            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14724
14725            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14726                editor
14727                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14728                    .unwrap()
14729            });
14730            cx.assert_editor_state(&expected_text);
14731            apply_additional_edits.await.unwrap();
14732        }
14733    }
14734}
14735
14736#[gpui::test]
14737async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14738    init_test(cx, |_| {});
14739    let mut cx = EditorLspTestContext::new_rust(
14740        lsp::ServerCapabilities {
14741            completion_provider: Some(lsp::CompletionOptions {
14742                resolve_provider: Some(true),
14743                ..Default::default()
14744            }),
14745            ..Default::default()
14746        },
14747        cx,
14748    )
14749    .await;
14750
14751    let initial_state = "SubˇError";
14752    let buffer_marked_text = "<Sub|Error>";
14753    let completion_text = "SubscriptionError";
14754    let expected_with_insert_mode = "SubscriptionErrorˇError";
14755    let expected_with_replace_mode = "SubscriptionErrorˇ";
14756
14757    update_test_language_settings(&mut cx, |settings| {
14758        settings.defaults.completions = Some(CompletionSettingsContent {
14759            words: Some(WordsCompletionMode::Disabled),
14760            words_min_length: Some(0),
14761            // set the opposite here to ensure that the action is overriding the default behavior
14762            lsp_insert_mode: Some(LspInsertMode::Insert),
14763            ..Default::default()
14764        });
14765    });
14766
14767    cx.set_state(initial_state);
14768    cx.update_editor(|editor, window, cx| {
14769        editor.show_completions(&ShowCompletions, window, cx);
14770    });
14771
14772    let counter = Arc::new(AtomicUsize::new(0));
14773    handle_completion_request_with_insert_and_replace(
14774        &mut cx,
14775        buffer_marked_text,
14776        vec![(completion_text, completion_text)],
14777        counter.clone(),
14778    )
14779    .await;
14780    cx.condition(|editor, _| editor.context_menu_visible())
14781        .await;
14782    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14783
14784    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14785        editor
14786            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14787            .unwrap()
14788    });
14789    cx.assert_editor_state(expected_with_replace_mode);
14790    handle_resolve_completion_request(&mut cx, None).await;
14791    apply_additional_edits.await.unwrap();
14792
14793    update_test_language_settings(&mut cx, |settings| {
14794        settings.defaults.completions = Some(CompletionSettingsContent {
14795            words: Some(WordsCompletionMode::Disabled),
14796            words_min_length: Some(0),
14797            // set the opposite here to ensure that the action is overriding the default behavior
14798            lsp_insert_mode: Some(LspInsertMode::Replace),
14799            ..Default::default()
14800        });
14801    });
14802
14803    cx.set_state(initial_state);
14804    cx.update_editor(|editor, window, cx| {
14805        editor.show_completions(&ShowCompletions, window, cx);
14806    });
14807    handle_completion_request_with_insert_and_replace(
14808        &mut cx,
14809        buffer_marked_text,
14810        vec![(completion_text, completion_text)],
14811        counter.clone(),
14812    )
14813    .await;
14814    cx.condition(|editor, _| editor.context_menu_visible())
14815        .await;
14816    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14817
14818    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14819        editor
14820            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14821            .unwrap()
14822    });
14823    cx.assert_editor_state(expected_with_insert_mode);
14824    handle_resolve_completion_request(&mut cx, None).await;
14825    apply_additional_edits.await.unwrap();
14826}
14827
14828#[gpui::test]
14829async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14830    init_test(cx, |_| {});
14831    let mut cx = EditorLspTestContext::new_rust(
14832        lsp::ServerCapabilities {
14833            completion_provider: Some(lsp::CompletionOptions {
14834                resolve_provider: Some(true),
14835                ..Default::default()
14836            }),
14837            ..Default::default()
14838        },
14839        cx,
14840    )
14841    .await;
14842
14843    // scenario: surrounding text matches completion text
14844    let completion_text = "to_offset";
14845    let initial_state = indoc! {"
14846        1. buf.to_offˇsuffix
14847        2. buf.to_offˇsuf
14848        3. buf.to_offˇfix
14849        4. buf.to_offˇ
14850        5. into_offˇensive
14851        6. ˇsuffix
14852        7. let ˇ //
14853        8. aaˇzz
14854        9. buf.to_off«zzzzzˇ»suffix
14855        10. buf.«ˇzzzzz»suffix
14856        11. to_off«ˇzzzzz»
14857
14858        buf.to_offˇsuffix  // newest cursor
14859    "};
14860    let completion_marked_buffer = indoc! {"
14861        1. buf.to_offsuffix
14862        2. buf.to_offsuf
14863        3. buf.to_offfix
14864        4. buf.to_off
14865        5. into_offensive
14866        6. suffix
14867        7. let  //
14868        8. aazz
14869        9. buf.to_offzzzzzsuffix
14870        10. buf.zzzzzsuffix
14871        11. to_offzzzzz
14872
14873        buf.<to_off|suffix>  // newest cursor
14874    "};
14875    let expected = indoc! {"
14876        1. buf.to_offsetˇ
14877        2. buf.to_offsetˇsuf
14878        3. buf.to_offsetˇfix
14879        4. buf.to_offsetˇ
14880        5. into_offsetˇensive
14881        6. to_offsetˇsuffix
14882        7. let to_offsetˇ //
14883        8. aato_offsetˇzz
14884        9. buf.to_offsetˇ
14885        10. buf.to_offsetˇsuffix
14886        11. to_offsetˇ
14887
14888        buf.to_offsetˇ  // newest cursor
14889    "};
14890    cx.set_state(initial_state);
14891    cx.update_editor(|editor, window, cx| {
14892        editor.show_completions(&ShowCompletions, window, cx);
14893    });
14894    handle_completion_request_with_insert_and_replace(
14895        &mut cx,
14896        completion_marked_buffer,
14897        vec![(completion_text, completion_text)],
14898        Arc::new(AtomicUsize::new(0)),
14899    )
14900    .await;
14901    cx.condition(|editor, _| editor.context_menu_visible())
14902        .await;
14903    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14904        editor
14905            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14906            .unwrap()
14907    });
14908    cx.assert_editor_state(expected);
14909    handle_resolve_completion_request(&mut cx, None).await;
14910    apply_additional_edits.await.unwrap();
14911
14912    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14913    let completion_text = "foo_and_bar";
14914    let initial_state = indoc! {"
14915        1. ooanbˇ
14916        2. zooanbˇ
14917        3. ooanbˇz
14918        4. zooanbˇz
14919        5. ooanˇ
14920        6. oanbˇ
14921
14922        ooanbˇ
14923    "};
14924    let completion_marked_buffer = indoc! {"
14925        1. ooanb
14926        2. zooanb
14927        3. ooanbz
14928        4. zooanbz
14929        5. ooan
14930        6. oanb
14931
14932        <ooanb|>
14933    "};
14934    let expected = indoc! {"
14935        1. foo_and_barˇ
14936        2. zfoo_and_barˇ
14937        3. foo_and_barˇz
14938        4. zfoo_and_barˇz
14939        5. ooanfoo_and_barˇ
14940        6. oanbfoo_and_barˇ
14941
14942        foo_and_barˇ
14943    "};
14944    cx.set_state(initial_state);
14945    cx.update_editor(|editor, window, cx| {
14946        editor.show_completions(&ShowCompletions, window, cx);
14947    });
14948    handle_completion_request_with_insert_and_replace(
14949        &mut cx,
14950        completion_marked_buffer,
14951        vec![(completion_text, completion_text)],
14952        Arc::new(AtomicUsize::new(0)),
14953    )
14954    .await;
14955    cx.condition(|editor, _| editor.context_menu_visible())
14956        .await;
14957    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14958        editor
14959            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14960            .unwrap()
14961    });
14962    cx.assert_editor_state(expected);
14963    handle_resolve_completion_request(&mut cx, None).await;
14964    apply_additional_edits.await.unwrap();
14965
14966    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14967    // (expects the same as if it was inserted at the end)
14968    let completion_text = "foo_and_bar";
14969    let initial_state = indoc! {"
14970        1. ooˇanb
14971        2. zooˇanb
14972        3. ooˇanbz
14973        4. zooˇanbz
14974
14975        ooˇanb
14976    "};
14977    let completion_marked_buffer = indoc! {"
14978        1. ooanb
14979        2. zooanb
14980        3. ooanbz
14981        4. zooanbz
14982
14983        <oo|anb>
14984    "};
14985    let expected = indoc! {"
14986        1. foo_and_barˇ
14987        2. zfoo_and_barˇ
14988        3. foo_and_barˇz
14989        4. zfoo_and_barˇz
14990
14991        foo_and_barˇ
14992    "};
14993    cx.set_state(initial_state);
14994    cx.update_editor(|editor, window, cx| {
14995        editor.show_completions(&ShowCompletions, window, cx);
14996    });
14997    handle_completion_request_with_insert_and_replace(
14998        &mut cx,
14999        completion_marked_buffer,
15000        vec![(completion_text, completion_text)],
15001        Arc::new(AtomicUsize::new(0)),
15002    )
15003    .await;
15004    cx.condition(|editor, _| editor.context_menu_visible())
15005        .await;
15006    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15007        editor
15008            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15009            .unwrap()
15010    });
15011    cx.assert_editor_state(expected);
15012    handle_resolve_completion_request(&mut cx, None).await;
15013    apply_additional_edits.await.unwrap();
15014}
15015
15016// This used to crash
15017#[gpui::test]
15018async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
15019    init_test(cx, |_| {});
15020
15021    let buffer_text = indoc! {"
15022        fn main() {
15023            10.satu;
15024
15025            //
15026            // separate cursors so they open in different excerpts (manually reproducible)
15027            //
15028
15029            10.satu20;
15030        }
15031    "};
15032    let multibuffer_text_with_selections = indoc! {"
15033        fn main() {
15034            10.satuˇ;
15035
15036            //
15037
15038            //
15039
15040            10.satuˇ20;
15041        }
15042    "};
15043    let expected_multibuffer = indoc! {"
15044        fn main() {
15045            10.saturating_sub()ˇ;
15046
15047            //
15048
15049            //
15050
15051            10.saturating_sub()ˇ;
15052        }
15053    "};
15054
15055    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
15056    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
15057
15058    let fs = FakeFs::new(cx.executor());
15059    fs.insert_tree(
15060        path!("/a"),
15061        json!({
15062            "main.rs": buffer_text,
15063        }),
15064    )
15065    .await;
15066
15067    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15068    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15069    language_registry.add(rust_lang());
15070    let mut fake_servers = language_registry.register_fake_lsp(
15071        "Rust",
15072        FakeLspAdapter {
15073            capabilities: lsp::ServerCapabilities {
15074                completion_provider: Some(lsp::CompletionOptions {
15075                    resolve_provider: None,
15076                    ..lsp::CompletionOptions::default()
15077                }),
15078                ..lsp::ServerCapabilities::default()
15079            },
15080            ..FakeLspAdapter::default()
15081        },
15082    );
15083    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15084    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15085    let buffer = project
15086        .update(cx, |project, cx| {
15087            project.open_local_buffer(path!("/a/main.rs"), cx)
15088        })
15089        .await
15090        .unwrap();
15091
15092    let multi_buffer = cx.new(|cx| {
15093        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
15094        multi_buffer.push_excerpts(
15095            buffer.clone(),
15096            [ExcerptRange::new(0..first_excerpt_end)],
15097            cx,
15098        );
15099        multi_buffer.push_excerpts(
15100            buffer.clone(),
15101            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
15102            cx,
15103        );
15104        multi_buffer
15105    });
15106
15107    let editor = workspace
15108        .update(cx, |_, window, cx| {
15109            cx.new(|cx| {
15110                Editor::new(
15111                    EditorMode::Full {
15112                        scale_ui_elements_with_buffer_font_size: false,
15113                        show_active_line_background: false,
15114                        sizing_behavior: SizingBehavior::Default,
15115                    },
15116                    multi_buffer.clone(),
15117                    Some(project.clone()),
15118                    window,
15119                    cx,
15120                )
15121            })
15122        })
15123        .unwrap();
15124
15125    let pane = workspace
15126        .update(cx, |workspace, _, _| workspace.active_pane().clone())
15127        .unwrap();
15128    pane.update_in(cx, |pane, window, cx| {
15129        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
15130    });
15131
15132    let fake_server = fake_servers.next().await.unwrap();
15133    cx.run_until_parked();
15134
15135    editor.update_in(cx, |editor, window, cx| {
15136        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15137            s.select_ranges([
15138                Point::new(1, 11)..Point::new(1, 11),
15139                Point::new(7, 11)..Point::new(7, 11),
15140            ])
15141        });
15142
15143        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
15144    });
15145
15146    editor.update_in(cx, |editor, window, cx| {
15147        editor.show_completions(&ShowCompletions, window, cx);
15148    });
15149
15150    fake_server
15151        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15152            let completion_item = lsp::CompletionItem {
15153                label: "saturating_sub()".into(),
15154                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15155                    lsp::InsertReplaceEdit {
15156                        new_text: "saturating_sub()".to_owned(),
15157                        insert: lsp::Range::new(
15158                            lsp::Position::new(7, 7),
15159                            lsp::Position::new(7, 11),
15160                        ),
15161                        replace: lsp::Range::new(
15162                            lsp::Position::new(7, 7),
15163                            lsp::Position::new(7, 13),
15164                        ),
15165                    },
15166                )),
15167                ..lsp::CompletionItem::default()
15168            };
15169
15170            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
15171        })
15172        .next()
15173        .await
15174        .unwrap();
15175
15176    cx.condition(&editor, |editor, _| editor.context_menu_visible())
15177        .await;
15178
15179    editor
15180        .update_in(cx, |editor, window, cx| {
15181            editor
15182                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15183                .unwrap()
15184        })
15185        .await
15186        .unwrap();
15187
15188    editor.update(cx, |editor, cx| {
15189        assert_text_with_selections(editor, expected_multibuffer, cx);
15190    })
15191}
15192
15193#[gpui::test]
15194async fn test_completion(cx: &mut TestAppContext) {
15195    init_test(cx, |_| {});
15196
15197    let mut cx = EditorLspTestContext::new_rust(
15198        lsp::ServerCapabilities {
15199            completion_provider: Some(lsp::CompletionOptions {
15200                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15201                resolve_provider: Some(true),
15202                ..Default::default()
15203            }),
15204            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15205            ..Default::default()
15206        },
15207        cx,
15208    )
15209    .await;
15210    let counter = Arc::new(AtomicUsize::new(0));
15211
15212    cx.set_state(indoc! {"
15213        oneˇ
15214        two
15215        three
15216    "});
15217    cx.simulate_keystroke(".");
15218    handle_completion_request(
15219        indoc! {"
15220            one.|<>
15221            two
15222            three
15223        "},
15224        vec!["first_completion", "second_completion"],
15225        true,
15226        counter.clone(),
15227        &mut cx,
15228    )
15229    .await;
15230    cx.condition(|editor, _| editor.context_menu_visible())
15231        .await;
15232    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15233
15234    let _handler = handle_signature_help_request(
15235        &mut cx,
15236        lsp::SignatureHelp {
15237            signatures: vec![lsp::SignatureInformation {
15238                label: "test signature".to_string(),
15239                documentation: None,
15240                parameters: Some(vec![lsp::ParameterInformation {
15241                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
15242                    documentation: None,
15243                }]),
15244                active_parameter: None,
15245            }],
15246            active_signature: None,
15247            active_parameter: None,
15248        },
15249    );
15250    cx.update_editor(|editor, window, cx| {
15251        assert!(
15252            !editor.signature_help_state.is_shown(),
15253            "No signature help was called for"
15254        );
15255        editor.show_signature_help(&ShowSignatureHelp, window, cx);
15256    });
15257    cx.run_until_parked();
15258    cx.update_editor(|editor, _, _| {
15259        assert!(
15260            !editor.signature_help_state.is_shown(),
15261            "No signature help should be shown when completions menu is open"
15262        );
15263    });
15264
15265    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15266        editor.context_menu_next(&Default::default(), window, cx);
15267        editor
15268            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15269            .unwrap()
15270    });
15271    cx.assert_editor_state(indoc! {"
15272        one.second_completionˇ
15273        two
15274        three
15275    "});
15276
15277    handle_resolve_completion_request(
15278        &mut cx,
15279        Some(vec![
15280            (
15281                //This overlaps with the primary completion edit which is
15282                //misbehavior from the LSP spec, test that we filter it out
15283                indoc! {"
15284                    one.second_ˇcompletion
15285                    two
15286                    threeˇ
15287                "},
15288                "overlapping additional edit",
15289            ),
15290            (
15291                indoc! {"
15292                    one.second_completion
15293                    two
15294                    threeˇ
15295                "},
15296                "\nadditional edit",
15297            ),
15298        ]),
15299    )
15300    .await;
15301    apply_additional_edits.await.unwrap();
15302    cx.assert_editor_state(indoc! {"
15303        one.second_completionˇ
15304        two
15305        three
15306        additional edit
15307    "});
15308
15309    cx.set_state(indoc! {"
15310        one.second_completion
15311        twoˇ
15312        threeˇ
15313        additional edit
15314    "});
15315    cx.simulate_keystroke(" ");
15316    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15317    cx.simulate_keystroke("s");
15318    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15319
15320    cx.assert_editor_state(indoc! {"
15321        one.second_completion
15322        two sˇ
15323        three sˇ
15324        additional edit
15325    "});
15326    handle_completion_request(
15327        indoc! {"
15328            one.second_completion
15329            two s
15330            three <s|>
15331            additional edit
15332        "},
15333        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15334        true,
15335        counter.clone(),
15336        &mut cx,
15337    )
15338    .await;
15339    cx.condition(|editor, _| editor.context_menu_visible())
15340        .await;
15341    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15342
15343    cx.simulate_keystroke("i");
15344
15345    handle_completion_request(
15346        indoc! {"
15347            one.second_completion
15348            two si
15349            three <si|>
15350            additional edit
15351        "},
15352        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15353        true,
15354        counter.clone(),
15355        &mut cx,
15356    )
15357    .await;
15358    cx.condition(|editor, _| editor.context_menu_visible())
15359        .await;
15360    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15361
15362    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15363        editor
15364            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15365            .unwrap()
15366    });
15367    cx.assert_editor_state(indoc! {"
15368        one.second_completion
15369        two sixth_completionˇ
15370        three sixth_completionˇ
15371        additional edit
15372    "});
15373
15374    apply_additional_edits.await.unwrap();
15375
15376    update_test_language_settings(&mut cx, |settings| {
15377        settings.defaults.show_completions_on_input = Some(false);
15378    });
15379    cx.set_state("editorˇ");
15380    cx.simulate_keystroke(".");
15381    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15382    cx.simulate_keystrokes("c l o");
15383    cx.assert_editor_state("editor.cloˇ");
15384    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15385    cx.update_editor(|editor, window, cx| {
15386        editor.show_completions(&ShowCompletions, window, cx);
15387    });
15388    handle_completion_request(
15389        "editor.<clo|>",
15390        vec!["close", "clobber"],
15391        true,
15392        counter.clone(),
15393        &mut cx,
15394    )
15395    .await;
15396    cx.condition(|editor, _| editor.context_menu_visible())
15397        .await;
15398    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
15399
15400    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15401        editor
15402            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15403            .unwrap()
15404    });
15405    cx.assert_editor_state("editor.clobberˇ");
15406    handle_resolve_completion_request(&mut cx, None).await;
15407    apply_additional_edits.await.unwrap();
15408}
15409
15410#[gpui::test]
15411async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
15412    init_test(cx, |_| {});
15413
15414    let fs = FakeFs::new(cx.executor());
15415    fs.insert_tree(
15416        path!("/a"),
15417        json!({
15418            "main.rs": "",
15419        }),
15420    )
15421    .await;
15422
15423    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15424    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15425    language_registry.add(rust_lang());
15426    let command_calls = Arc::new(AtomicUsize::new(0));
15427    let registered_command = "_the/command";
15428
15429    let closure_command_calls = command_calls.clone();
15430    let mut fake_servers = language_registry.register_fake_lsp(
15431        "Rust",
15432        FakeLspAdapter {
15433            capabilities: lsp::ServerCapabilities {
15434                completion_provider: Some(lsp::CompletionOptions {
15435                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15436                    ..lsp::CompletionOptions::default()
15437                }),
15438                execute_command_provider: Some(lsp::ExecuteCommandOptions {
15439                    commands: vec![registered_command.to_owned()],
15440                    ..lsp::ExecuteCommandOptions::default()
15441                }),
15442                ..lsp::ServerCapabilities::default()
15443            },
15444            initializer: Some(Box::new(move |fake_server| {
15445                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15446                    move |params, _| async move {
15447                        Ok(Some(lsp::CompletionResponse::Array(vec![
15448                            lsp::CompletionItem {
15449                                label: "registered_command".to_owned(),
15450                                text_edit: gen_text_edit(&params, ""),
15451                                command: Some(lsp::Command {
15452                                    title: registered_command.to_owned(),
15453                                    command: "_the/command".to_owned(),
15454                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
15455                                }),
15456                                ..lsp::CompletionItem::default()
15457                            },
15458                            lsp::CompletionItem {
15459                                label: "unregistered_command".to_owned(),
15460                                text_edit: gen_text_edit(&params, ""),
15461                                command: Some(lsp::Command {
15462                                    title: "????????????".to_owned(),
15463                                    command: "????????????".to_owned(),
15464                                    arguments: Some(vec![serde_json::Value::Null]),
15465                                }),
15466                                ..lsp::CompletionItem::default()
15467                            },
15468                        ])))
15469                    },
15470                );
15471                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
15472                    let command_calls = closure_command_calls.clone();
15473                    move |params, _| {
15474                        assert_eq!(params.command, registered_command);
15475                        let command_calls = command_calls.clone();
15476                        async move {
15477                            command_calls.fetch_add(1, atomic::Ordering::Release);
15478                            Ok(Some(json!(null)))
15479                        }
15480                    }
15481                });
15482            })),
15483            ..FakeLspAdapter::default()
15484        },
15485    );
15486    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15487    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15488    let editor = workspace
15489        .update(cx, |workspace, window, cx| {
15490            workspace.open_abs_path(
15491                PathBuf::from(path!("/a/main.rs")),
15492                OpenOptions::default(),
15493                window,
15494                cx,
15495            )
15496        })
15497        .unwrap()
15498        .await
15499        .unwrap()
15500        .downcast::<Editor>()
15501        .unwrap();
15502    let _fake_server = fake_servers.next().await.unwrap();
15503    cx.run_until_parked();
15504
15505    editor.update_in(cx, |editor, window, cx| {
15506        cx.focus_self(window);
15507        editor.move_to_end(&MoveToEnd, window, cx);
15508        editor.handle_input(".", window, cx);
15509    });
15510    cx.run_until_parked();
15511    editor.update(cx, |editor, _| {
15512        assert!(editor.context_menu_visible());
15513        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15514        {
15515            let completion_labels = menu
15516                .completions
15517                .borrow()
15518                .iter()
15519                .map(|c| c.label.text.clone())
15520                .collect::<Vec<_>>();
15521            assert_eq!(
15522                completion_labels,
15523                &["registered_command", "unregistered_command",],
15524            );
15525        } else {
15526            panic!("expected completion menu to be open");
15527        }
15528    });
15529
15530    editor
15531        .update_in(cx, |editor, window, cx| {
15532            editor
15533                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15534                .unwrap()
15535        })
15536        .await
15537        .unwrap();
15538    cx.run_until_parked();
15539    assert_eq!(
15540        command_calls.load(atomic::Ordering::Acquire),
15541        1,
15542        "For completion with a registered command, Zed should send a command execution request",
15543    );
15544
15545    editor.update_in(cx, |editor, window, cx| {
15546        cx.focus_self(window);
15547        editor.handle_input(".", window, cx);
15548    });
15549    cx.run_until_parked();
15550    editor.update(cx, |editor, _| {
15551        assert!(editor.context_menu_visible());
15552        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15553        {
15554            let completion_labels = menu
15555                .completions
15556                .borrow()
15557                .iter()
15558                .map(|c| c.label.text.clone())
15559                .collect::<Vec<_>>();
15560            assert_eq!(
15561                completion_labels,
15562                &["registered_command", "unregistered_command",],
15563            );
15564        } else {
15565            panic!("expected completion menu to be open");
15566        }
15567    });
15568    editor
15569        .update_in(cx, |editor, window, cx| {
15570            editor.context_menu_next(&Default::default(), window, cx);
15571            editor
15572                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15573                .unwrap()
15574        })
15575        .await
15576        .unwrap();
15577    cx.run_until_parked();
15578    assert_eq!(
15579        command_calls.load(atomic::Ordering::Acquire),
15580        1,
15581        "For completion with an unregistered command, Zed should not send a command execution request",
15582    );
15583}
15584
15585#[gpui::test]
15586async fn test_completion_reuse(cx: &mut TestAppContext) {
15587    init_test(cx, |_| {});
15588
15589    let mut cx = EditorLspTestContext::new_rust(
15590        lsp::ServerCapabilities {
15591            completion_provider: Some(lsp::CompletionOptions {
15592                trigger_characters: Some(vec![".".to_string()]),
15593                ..Default::default()
15594            }),
15595            ..Default::default()
15596        },
15597        cx,
15598    )
15599    .await;
15600
15601    let counter = Arc::new(AtomicUsize::new(0));
15602    cx.set_state("objˇ");
15603    cx.simulate_keystroke(".");
15604
15605    // Initial completion request returns complete results
15606    let is_incomplete = false;
15607    handle_completion_request(
15608        "obj.|<>",
15609        vec!["a", "ab", "abc"],
15610        is_incomplete,
15611        counter.clone(),
15612        &mut cx,
15613    )
15614    .await;
15615    cx.run_until_parked();
15616    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15617    cx.assert_editor_state("obj.ˇ");
15618    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15619
15620    // Type "a" - filters existing completions
15621    cx.simulate_keystroke("a");
15622    cx.run_until_parked();
15623    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15624    cx.assert_editor_state("obj.aˇ");
15625    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15626
15627    // Type "b" - filters existing completions
15628    cx.simulate_keystroke("b");
15629    cx.run_until_parked();
15630    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15631    cx.assert_editor_state("obj.abˇ");
15632    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15633
15634    // Type "c" - filters existing completions
15635    cx.simulate_keystroke("c");
15636    cx.run_until_parked();
15637    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15638    cx.assert_editor_state("obj.abcˇ");
15639    check_displayed_completions(vec!["abc"], &mut cx);
15640
15641    // Backspace to delete "c" - filters existing completions
15642    cx.update_editor(|editor, window, cx| {
15643        editor.backspace(&Backspace, window, cx);
15644    });
15645    cx.run_until_parked();
15646    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15647    cx.assert_editor_state("obj.abˇ");
15648    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15649
15650    // Moving cursor to the left dismisses menu.
15651    cx.update_editor(|editor, window, cx| {
15652        editor.move_left(&MoveLeft, window, cx);
15653    });
15654    cx.run_until_parked();
15655    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15656    cx.assert_editor_state("obj.aˇb");
15657    cx.update_editor(|editor, _, _| {
15658        assert_eq!(editor.context_menu_visible(), false);
15659    });
15660
15661    // Type "b" - new request
15662    cx.simulate_keystroke("b");
15663    let is_incomplete = false;
15664    handle_completion_request(
15665        "obj.<ab|>a",
15666        vec!["ab", "abc"],
15667        is_incomplete,
15668        counter.clone(),
15669        &mut cx,
15670    )
15671    .await;
15672    cx.run_until_parked();
15673    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15674    cx.assert_editor_state("obj.abˇb");
15675    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15676
15677    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15678    cx.update_editor(|editor, window, cx| {
15679        editor.backspace(&Backspace, window, cx);
15680    });
15681    let is_incomplete = false;
15682    handle_completion_request(
15683        "obj.<a|>b",
15684        vec!["a", "ab", "abc"],
15685        is_incomplete,
15686        counter.clone(),
15687        &mut cx,
15688    )
15689    .await;
15690    cx.run_until_parked();
15691    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15692    cx.assert_editor_state("obj.aˇb");
15693    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15694
15695    // Backspace to delete "a" - dismisses menu.
15696    cx.update_editor(|editor, window, cx| {
15697        editor.backspace(&Backspace, window, cx);
15698    });
15699    cx.run_until_parked();
15700    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15701    cx.assert_editor_state("obj.ˇb");
15702    cx.update_editor(|editor, _, _| {
15703        assert_eq!(editor.context_menu_visible(), false);
15704    });
15705}
15706
15707#[gpui::test]
15708async fn test_word_completion(cx: &mut TestAppContext) {
15709    let lsp_fetch_timeout_ms = 10;
15710    init_test(cx, |language_settings| {
15711        language_settings.defaults.completions = Some(CompletionSettingsContent {
15712            words_min_length: Some(0),
15713            lsp_fetch_timeout_ms: Some(10),
15714            lsp_insert_mode: Some(LspInsertMode::Insert),
15715            ..Default::default()
15716        });
15717    });
15718
15719    let mut cx = EditorLspTestContext::new_rust(
15720        lsp::ServerCapabilities {
15721            completion_provider: Some(lsp::CompletionOptions {
15722                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15723                ..lsp::CompletionOptions::default()
15724            }),
15725            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15726            ..lsp::ServerCapabilities::default()
15727        },
15728        cx,
15729    )
15730    .await;
15731
15732    let throttle_completions = Arc::new(AtomicBool::new(false));
15733
15734    let lsp_throttle_completions = throttle_completions.clone();
15735    let _completion_requests_handler =
15736        cx.lsp
15737            .server
15738            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15739                let lsp_throttle_completions = lsp_throttle_completions.clone();
15740                let cx = cx.clone();
15741                async move {
15742                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15743                        cx.background_executor()
15744                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15745                            .await;
15746                    }
15747                    Ok(Some(lsp::CompletionResponse::Array(vec![
15748                        lsp::CompletionItem {
15749                            label: "first".into(),
15750                            ..lsp::CompletionItem::default()
15751                        },
15752                        lsp::CompletionItem {
15753                            label: "last".into(),
15754                            ..lsp::CompletionItem::default()
15755                        },
15756                    ])))
15757                }
15758            });
15759
15760    cx.set_state(indoc! {"
15761        oneˇ
15762        two
15763        three
15764    "});
15765    cx.simulate_keystroke(".");
15766    cx.executor().run_until_parked();
15767    cx.condition(|editor, _| editor.context_menu_visible())
15768        .await;
15769    cx.update_editor(|editor, window, cx| {
15770        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15771        {
15772            assert_eq!(
15773                completion_menu_entries(menu),
15774                &["first", "last"],
15775                "When LSP server is fast to reply, no fallback word completions are used"
15776            );
15777        } else {
15778            panic!("expected completion menu to be open");
15779        }
15780        editor.cancel(&Cancel, window, cx);
15781    });
15782    cx.executor().run_until_parked();
15783    cx.condition(|editor, _| !editor.context_menu_visible())
15784        .await;
15785
15786    throttle_completions.store(true, atomic::Ordering::Release);
15787    cx.simulate_keystroke(".");
15788    cx.executor()
15789        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15790    cx.executor().run_until_parked();
15791    cx.condition(|editor, _| editor.context_menu_visible())
15792        .await;
15793    cx.update_editor(|editor, _, _| {
15794        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15795        {
15796            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15797                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15798        } else {
15799            panic!("expected completion menu to be open");
15800        }
15801    });
15802}
15803
15804#[gpui::test]
15805async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15806    init_test(cx, |language_settings| {
15807        language_settings.defaults.completions = Some(CompletionSettingsContent {
15808            words: Some(WordsCompletionMode::Enabled),
15809            words_min_length: Some(0),
15810            lsp_insert_mode: Some(LspInsertMode::Insert),
15811            ..Default::default()
15812        });
15813    });
15814
15815    let mut cx = EditorLspTestContext::new_rust(
15816        lsp::ServerCapabilities {
15817            completion_provider: Some(lsp::CompletionOptions {
15818                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15819                ..lsp::CompletionOptions::default()
15820            }),
15821            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15822            ..lsp::ServerCapabilities::default()
15823        },
15824        cx,
15825    )
15826    .await;
15827
15828    let _completion_requests_handler =
15829        cx.lsp
15830            .server
15831            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15832                Ok(Some(lsp::CompletionResponse::Array(vec![
15833                    lsp::CompletionItem {
15834                        label: "first".into(),
15835                        ..lsp::CompletionItem::default()
15836                    },
15837                    lsp::CompletionItem {
15838                        label: "last".into(),
15839                        ..lsp::CompletionItem::default()
15840                    },
15841                ])))
15842            });
15843
15844    cx.set_state(indoc! {"ˇ
15845        first
15846        last
15847        second
15848    "});
15849    cx.simulate_keystroke(".");
15850    cx.executor().run_until_parked();
15851    cx.condition(|editor, _| editor.context_menu_visible())
15852        .await;
15853    cx.update_editor(|editor, _, _| {
15854        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15855        {
15856            assert_eq!(
15857                completion_menu_entries(menu),
15858                &["first", "last", "second"],
15859                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15860            );
15861        } else {
15862            panic!("expected completion menu to be open");
15863        }
15864    });
15865}
15866
15867#[gpui::test]
15868async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15869    init_test(cx, |language_settings| {
15870        language_settings.defaults.completions = Some(CompletionSettingsContent {
15871            words: Some(WordsCompletionMode::Disabled),
15872            words_min_length: Some(0),
15873            lsp_insert_mode: Some(LspInsertMode::Insert),
15874            ..Default::default()
15875        });
15876    });
15877
15878    let mut cx = EditorLspTestContext::new_rust(
15879        lsp::ServerCapabilities {
15880            completion_provider: Some(lsp::CompletionOptions {
15881                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15882                ..lsp::CompletionOptions::default()
15883            }),
15884            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15885            ..lsp::ServerCapabilities::default()
15886        },
15887        cx,
15888    )
15889    .await;
15890
15891    let _completion_requests_handler =
15892        cx.lsp
15893            .server
15894            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15895                panic!("LSP completions should not be queried when dealing with word completions")
15896            });
15897
15898    cx.set_state(indoc! {"ˇ
15899        first
15900        last
15901        second
15902    "});
15903    cx.update_editor(|editor, window, cx| {
15904        editor.show_word_completions(&ShowWordCompletions, window, cx);
15905    });
15906    cx.executor().run_until_parked();
15907    cx.condition(|editor, _| editor.context_menu_visible())
15908        .await;
15909    cx.update_editor(|editor, _, _| {
15910        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15911        {
15912            assert_eq!(
15913                completion_menu_entries(menu),
15914                &["first", "last", "second"],
15915                "`ShowWordCompletions` action should show word completions"
15916            );
15917        } else {
15918            panic!("expected completion menu to be open");
15919        }
15920    });
15921
15922    cx.simulate_keystroke("l");
15923    cx.executor().run_until_parked();
15924    cx.condition(|editor, _| editor.context_menu_visible())
15925        .await;
15926    cx.update_editor(|editor, _, _| {
15927        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15928        {
15929            assert_eq!(
15930                completion_menu_entries(menu),
15931                &["last"],
15932                "After showing word completions, further editing should filter them and not query the LSP"
15933            );
15934        } else {
15935            panic!("expected completion menu to be open");
15936        }
15937    });
15938}
15939
15940#[gpui::test]
15941async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15942    init_test(cx, |language_settings| {
15943        language_settings.defaults.completions = Some(CompletionSettingsContent {
15944            words_min_length: Some(0),
15945            lsp: Some(false),
15946            lsp_insert_mode: Some(LspInsertMode::Insert),
15947            ..Default::default()
15948        });
15949    });
15950
15951    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15952
15953    cx.set_state(indoc! {"ˇ
15954        0_usize
15955        let
15956        33
15957        4.5f32
15958    "});
15959    cx.update_editor(|editor, window, cx| {
15960        editor.show_completions(&ShowCompletions, window, cx);
15961    });
15962    cx.executor().run_until_parked();
15963    cx.condition(|editor, _| editor.context_menu_visible())
15964        .await;
15965    cx.update_editor(|editor, window, cx| {
15966        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15967        {
15968            assert_eq!(
15969                completion_menu_entries(menu),
15970                &["let"],
15971                "With no digits in the completion query, no digits should be in the word completions"
15972            );
15973        } else {
15974            panic!("expected completion menu to be open");
15975        }
15976        editor.cancel(&Cancel, window, cx);
15977    });
15978
15979    cx.set_state(indoc! {"15980        0_usize
15981        let
15982        3
15983        33.35f32
15984    "});
15985    cx.update_editor(|editor, window, cx| {
15986        editor.show_completions(&ShowCompletions, window, cx);
15987    });
15988    cx.executor().run_until_parked();
15989    cx.condition(|editor, _| editor.context_menu_visible())
15990        .await;
15991    cx.update_editor(|editor, _, _| {
15992        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15993        {
15994            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15995                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15996        } else {
15997            panic!("expected completion menu to be open");
15998        }
15999    });
16000}
16001
16002#[gpui::test]
16003async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
16004    init_test(cx, |language_settings| {
16005        language_settings.defaults.completions = Some(CompletionSettingsContent {
16006            words: Some(WordsCompletionMode::Enabled),
16007            words_min_length: Some(3),
16008            lsp_insert_mode: Some(LspInsertMode::Insert),
16009            ..Default::default()
16010        });
16011    });
16012
16013    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16014    cx.set_state(indoc! {"ˇ
16015        wow
16016        wowen
16017        wowser
16018    "});
16019    cx.simulate_keystroke("w");
16020    cx.executor().run_until_parked();
16021    cx.update_editor(|editor, _, _| {
16022        if editor.context_menu.borrow_mut().is_some() {
16023            panic!(
16024                "expected completion menu to be hidden, as words completion threshold is not met"
16025            );
16026        }
16027    });
16028
16029    cx.update_editor(|editor, window, cx| {
16030        editor.show_word_completions(&ShowWordCompletions, window, cx);
16031    });
16032    cx.executor().run_until_parked();
16033    cx.update_editor(|editor, window, cx| {
16034        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16035        {
16036            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");
16037        } else {
16038            panic!("expected completion menu to be open after the word completions are called with an action");
16039        }
16040
16041        editor.cancel(&Cancel, window, cx);
16042    });
16043    cx.update_editor(|editor, _, _| {
16044        if editor.context_menu.borrow_mut().is_some() {
16045            panic!("expected completion menu to be hidden after canceling");
16046        }
16047    });
16048
16049    cx.simulate_keystroke("o");
16050    cx.executor().run_until_parked();
16051    cx.update_editor(|editor, _, _| {
16052        if editor.context_menu.borrow_mut().is_some() {
16053            panic!(
16054                "expected completion menu to be hidden, as words completion threshold is not met still"
16055            );
16056        }
16057    });
16058
16059    cx.simulate_keystroke("w");
16060    cx.executor().run_until_parked();
16061    cx.update_editor(|editor, _, _| {
16062        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16063        {
16064            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
16065        } else {
16066            panic!("expected completion menu to be open after the word completions threshold is met");
16067        }
16068    });
16069}
16070
16071#[gpui::test]
16072async fn test_word_completions_disabled(cx: &mut TestAppContext) {
16073    init_test(cx, |language_settings| {
16074        language_settings.defaults.completions = Some(CompletionSettingsContent {
16075            words: Some(WordsCompletionMode::Enabled),
16076            words_min_length: Some(0),
16077            lsp_insert_mode: Some(LspInsertMode::Insert),
16078            ..Default::default()
16079        });
16080    });
16081
16082    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16083    cx.update_editor(|editor, _, _| {
16084        editor.disable_word_completions();
16085    });
16086    cx.set_state(indoc! {"ˇ
16087        wow
16088        wowen
16089        wowser
16090    "});
16091    cx.simulate_keystroke("w");
16092    cx.executor().run_until_parked();
16093    cx.update_editor(|editor, _, _| {
16094        if editor.context_menu.borrow_mut().is_some() {
16095            panic!(
16096                "expected completion menu to be hidden, as words completion are disabled for this editor"
16097            );
16098        }
16099    });
16100
16101    cx.update_editor(|editor, window, cx| {
16102        editor.show_word_completions(&ShowWordCompletions, window, cx);
16103    });
16104    cx.executor().run_until_parked();
16105    cx.update_editor(|editor, _, _| {
16106        if editor.context_menu.borrow_mut().is_some() {
16107            panic!(
16108                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
16109            );
16110        }
16111    });
16112}
16113
16114#[gpui::test]
16115async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
16116    init_test(cx, |language_settings| {
16117        language_settings.defaults.completions = Some(CompletionSettingsContent {
16118            words: Some(WordsCompletionMode::Disabled),
16119            words_min_length: Some(0),
16120            lsp_insert_mode: Some(LspInsertMode::Insert),
16121            ..Default::default()
16122        });
16123    });
16124
16125    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16126    cx.update_editor(|editor, _, _| {
16127        editor.set_completion_provider(None);
16128    });
16129    cx.set_state(indoc! {"ˇ
16130        wow
16131        wowen
16132        wowser
16133    "});
16134    cx.simulate_keystroke("w");
16135    cx.executor().run_until_parked();
16136    cx.update_editor(|editor, _, _| {
16137        if editor.context_menu.borrow_mut().is_some() {
16138            panic!("expected completion menu to be hidden, as disabled in settings");
16139        }
16140    });
16141}
16142
16143fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
16144    let position = || lsp::Position {
16145        line: params.text_document_position.position.line,
16146        character: params.text_document_position.position.character,
16147    };
16148    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16149        range: lsp::Range {
16150            start: position(),
16151            end: position(),
16152        },
16153        new_text: text.to_string(),
16154    }))
16155}
16156
16157#[gpui::test]
16158async fn test_multiline_completion(cx: &mut TestAppContext) {
16159    init_test(cx, |_| {});
16160
16161    let fs = FakeFs::new(cx.executor());
16162    fs.insert_tree(
16163        path!("/a"),
16164        json!({
16165            "main.ts": "a",
16166        }),
16167    )
16168    .await;
16169
16170    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16171    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16172    let typescript_language = Arc::new(Language::new(
16173        LanguageConfig {
16174            name: "TypeScript".into(),
16175            matcher: LanguageMatcher {
16176                path_suffixes: vec!["ts".to_string()],
16177                ..LanguageMatcher::default()
16178            },
16179            line_comments: vec!["// ".into()],
16180            ..LanguageConfig::default()
16181        },
16182        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16183    ));
16184    language_registry.add(typescript_language.clone());
16185    let mut fake_servers = language_registry.register_fake_lsp(
16186        "TypeScript",
16187        FakeLspAdapter {
16188            capabilities: lsp::ServerCapabilities {
16189                completion_provider: Some(lsp::CompletionOptions {
16190                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16191                    ..lsp::CompletionOptions::default()
16192                }),
16193                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16194                ..lsp::ServerCapabilities::default()
16195            },
16196            // Emulate vtsls label generation
16197            label_for_completion: Some(Box::new(|item, _| {
16198                let text = if let Some(description) = item
16199                    .label_details
16200                    .as_ref()
16201                    .and_then(|label_details| label_details.description.as_ref())
16202                {
16203                    format!("{} {}", item.label, description)
16204                } else if let Some(detail) = &item.detail {
16205                    format!("{} {}", item.label, detail)
16206                } else {
16207                    item.label.clone()
16208                };
16209                Some(language::CodeLabel::plain(text, None))
16210            })),
16211            ..FakeLspAdapter::default()
16212        },
16213    );
16214    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16215    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16216    let worktree_id = workspace
16217        .update(cx, |workspace, _window, cx| {
16218            workspace.project().update(cx, |project, cx| {
16219                project.worktrees(cx).next().unwrap().read(cx).id()
16220            })
16221        })
16222        .unwrap();
16223    let _buffer = project
16224        .update(cx, |project, cx| {
16225            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
16226        })
16227        .await
16228        .unwrap();
16229    let editor = workspace
16230        .update(cx, |workspace, window, cx| {
16231            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
16232        })
16233        .unwrap()
16234        .await
16235        .unwrap()
16236        .downcast::<Editor>()
16237        .unwrap();
16238    let fake_server = fake_servers.next().await.unwrap();
16239    cx.run_until_parked();
16240
16241    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
16242    let multiline_label_2 = "a\nb\nc\n";
16243    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
16244    let multiline_description = "d\ne\nf\n";
16245    let multiline_detail_2 = "g\nh\ni\n";
16246
16247    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
16248        move |params, _| async move {
16249            Ok(Some(lsp::CompletionResponse::Array(vec![
16250                lsp::CompletionItem {
16251                    label: multiline_label.to_string(),
16252                    text_edit: gen_text_edit(&params, "new_text_1"),
16253                    ..lsp::CompletionItem::default()
16254                },
16255                lsp::CompletionItem {
16256                    label: "single line label 1".to_string(),
16257                    detail: Some(multiline_detail.to_string()),
16258                    text_edit: gen_text_edit(&params, "new_text_2"),
16259                    ..lsp::CompletionItem::default()
16260                },
16261                lsp::CompletionItem {
16262                    label: "single line label 2".to_string(),
16263                    label_details: Some(lsp::CompletionItemLabelDetails {
16264                        description: Some(multiline_description.to_string()),
16265                        detail: None,
16266                    }),
16267                    text_edit: gen_text_edit(&params, "new_text_2"),
16268                    ..lsp::CompletionItem::default()
16269                },
16270                lsp::CompletionItem {
16271                    label: multiline_label_2.to_string(),
16272                    detail: Some(multiline_detail_2.to_string()),
16273                    text_edit: gen_text_edit(&params, "new_text_3"),
16274                    ..lsp::CompletionItem::default()
16275                },
16276                lsp::CompletionItem {
16277                    label: "Label with many     spaces and \t but without newlines".to_string(),
16278                    detail: Some(
16279                        "Details with many     spaces and \t but without newlines".to_string(),
16280                    ),
16281                    text_edit: gen_text_edit(&params, "new_text_4"),
16282                    ..lsp::CompletionItem::default()
16283                },
16284            ])))
16285        },
16286    );
16287
16288    editor.update_in(cx, |editor, window, cx| {
16289        cx.focus_self(window);
16290        editor.move_to_end(&MoveToEnd, window, cx);
16291        editor.handle_input(".", window, cx);
16292    });
16293    cx.run_until_parked();
16294    completion_handle.next().await.unwrap();
16295
16296    editor.update(cx, |editor, _| {
16297        assert!(editor.context_menu_visible());
16298        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16299        {
16300            let completion_labels = menu
16301                .completions
16302                .borrow()
16303                .iter()
16304                .map(|c| c.label.text.clone())
16305                .collect::<Vec<_>>();
16306            assert_eq!(
16307                completion_labels,
16308                &[
16309                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
16310                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
16311                    "single line label 2 d e f ",
16312                    "a b c g h i ",
16313                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
16314                ],
16315                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
16316            );
16317
16318            for completion in menu
16319                .completions
16320                .borrow()
16321                .iter() {
16322                    assert_eq!(
16323                        completion.label.filter_range,
16324                        0..completion.label.text.len(),
16325                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
16326                    );
16327                }
16328        } else {
16329            panic!("expected completion menu to be open");
16330        }
16331    });
16332}
16333
16334#[gpui::test]
16335async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
16336    init_test(cx, |_| {});
16337    let mut cx = EditorLspTestContext::new_rust(
16338        lsp::ServerCapabilities {
16339            completion_provider: Some(lsp::CompletionOptions {
16340                trigger_characters: Some(vec![".".to_string()]),
16341                ..Default::default()
16342            }),
16343            ..Default::default()
16344        },
16345        cx,
16346    )
16347    .await;
16348    cx.lsp
16349        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16350            Ok(Some(lsp::CompletionResponse::Array(vec![
16351                lsp::CompletionItem {
16352                    label: "first".into(),
16353                    ..Default::default()
16354                },
16355                lsp::CompletionItem {
16356                    label: "last".into(),
16357                    ..Default::default()
16358                },
16359            ])))
16360        });
16361    cx.set_state("variableˇ");
16362    cx.simulate_keystroke(".");
16363    cx.executor().run_until_parked();
16364
16365    cx.update_editor(|editor, _, _| {
16366        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16367        {
16368            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
16369        } else {
16370            panic!("expected completion menu to be open");
16371        }
16372    });
16373
16374    cx.update_editor(|editor, window, cx| {
16375        editor.move_page_down(&MovePageDown::default(), window, cx);
16376        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16377        {
16378            assert!(
16379                menu.selected_item == 1,
16380                "expected PageDown to select the last item from the context menu"
16381            );
16382        } else {
16383            panic!("expected completion menu to stay open after PageDown");
16384        }
16385    });
16386
16387    cx.update_editor(|editor, window, cx| {
16388        editor.move_page_up(&MovePageUp::default(), window, cx);
16389        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16390        {
16391            assert!(
16392                menu.selected_item == 0,
16393                "expected PageUp to select the first item from the context menu"
16394            );
16395        } else {
16396            panic!("expected completion menu to stay open after PageUp");
16397        }
16398    });
16399}
16400
16401#[gpui::test]
16402async fn test_as_is_completions(cx: &mut TestAppContext) {
16403    init_test(cx, |_| {});
16404    let mut cx = EditorLspTestContext::new_rust(
16405        lsp::ServerCapabilities {
16406            completion_provider: Some(lsp::CompletionOptions {
16407                ..Default::default()
16408            }),
16409            ..Default::default()
16410        },
16411        cx,
16412    )
16413    .await;
16414    cx.lsp
16415        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16416            Ok(Some(lsp::CompletionResponse::Array(vec![
16417                lsp::CompletionItem {
16418                    label: "unsafe".into(),
16419                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16420                        range: lsp::Range {
16421                            start: lsp::Position {
16422                                line: 1,
16423                                character: 2,
16424                            },
16425                            end: lsp::Position {
16426                                line: 1,
16427                                character: 3,
16428                            },
16429                        },
16430                        new_text: "unsafe".to_string(),
16431                    })),
16432                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
16433                    ..Default::default()
16434                },
16435            ])))
16436        });
16437    cx.set_state("fn a() {}\n");
16438    cx.executor().run_until_parked();
16439    cx.update_editor(|editor, window, cx| {
16440        editor.trigger_completion_on_input("n", true, window, cx)
16441    });
16442    cx.executor().run_until_parked();
16443
16444    cx.update_editor(|editor, window, cx| {
16445        editor.confirm_completion(&Default::default(), window, cx)
16446    });
16447    cx.executor().run_until_parked();
16448    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
16449}
16450
16451#[gpui::test]
16452async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
16453    init_test(cx, |_| {});
16454    let language =
16455        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
16456    let mut cx = EditorLspTestContext::new(
16457        language,
16458        lsp::ServerCapabilities {
16459            completion_provider: Some(lsp::CompletionOptions {
16460                ..lsp::CompletionOptions::default()
16461            }),
16462            ..lsp::ServerCapabilities::default()
16463        },
16464        cx,
16465    )
16466    .await;
16467
16468    cx.set_state(
16469        "#ifndef BAR_H
16470#define BAR_H
16471
16472#include <stdbool.h>
16473
16474int fn_branch(bool do_branch1, bool do_branch2);
16475
16476#endif // BAR_H
16477ˇ",
16478    );
16479    cx.executor().run_until_parked();
16480    cx.update_editor(|editor, window, cx| {
16481        editor.handle_input("#", window, cx);
16482    });
16483    cx.executor().run_until_parked();
16484    cx.update_editor(|editor, window, cx| {
16485        editor.handle_input("i", window, cx);
16486    });
16487    cx.executor().run_until_parked();
16488    cx.update_editor(|editor, window, cx| {
16489        editor.handle_input("n", window, cx);
16490    });
16491    cx.executor().run_until_parked();
16492    cx.assert_editor_state(
16493        "#ifndef BAR_H
16494#define BAR_H
16495
16496#include <stdbool.h>
16497
16498int fn_branch(bool do_branch1, bool do_branch2);
16499
16500#endif // BAR_H
16501#inˇ",
16502    );
16503
16504    cx.lsp
16505        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16506            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16507                is_incomplete: false,
16508                item_defaults: None,
16509                items: vec![lsp::CompletionItem {
16510                    kind: Some(lsp::CompletionItemKind::SNIPPET),
16511                    label_details: Some(lsp::CompletionItemLabelDetails {
16512                        detail: Some("header".to_string()),
16513                        description: None,
16514                    }),
16515                    label: " include".to_string(),
16516                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16517                        range: lsp::Range {
16518                            start: lsp::Position {
16519                                line: 8,
16520                                character: 1,
16521                            },
16522                            end: lsp::Position {
16523                                line: 8,
16524                                character: 1,
16525                            },
16526                        },
16527                        new_text: "include \"$0\"".to_string(),
16528                    })),
16529                    sort_text: Some("40b67681include".to_string()),
16530                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16531                    filter_text: Some("include".to_string()),
16532                    insert_text: Some("include \"$0\"".to_string()),
16533                    ..lsp::CompletionItem::default()
16534                }],
16535            })))
16536        });
16537    cx.update_editor(|editor, window, cx| {
16538        editor.show_completions(&ShowCompletions, window, cx);
16539    });
16540    cx.executor().run_until_parked();
16541    cx.update_editor(|editor, window, cx| {
16542        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16543    });
16544    cx.executor().run_until_parked();
16545    cx.assert_editor_state(
16546        "#ifndef BAR_H
16547#define BAR_H
16548
16549#include <stdbool.h>
16550
16551int fn_branch(bool do_branch1, bool do_branch2);
16552
16553#endif // BAR_H
16554#include \"ˇ\"",
16555    );
16556
16557    cx.lsp
16558        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16559            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16560                is_incomplete: true,
16561                item_defaults: None,
16562                items: vec![lsp::CompletionItem {
16563                    kind: Some(lsp::CompletionItemKind::FILE),
16564                    label: "AGL/".to_string(),
16565                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16566                        range: lsp::Range {
16567                            start: lsp::Position {
16568                                line: 8,
16569                                character: 10,
16570                            },
16571                            end: lsp::Position {
16572                                line: 8,
16573                                character: 11,
16574                            },
16575                        },
16576                        new_text: "AGL/".to_string(),
16577                    })),
16578                    sort_text: Some("40b67681AGL/".to_string()),
16579                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16580                    filter_text: Some("AGL/".to_string()),
16581                    insert_text: Some("AGL/".to_string()),
16582                    ..lsp::CompletionItem::default()
16583                }],
16584            })))
16585        });
16586    cx.update_editor(|editor, window, cx| {
16587        editor.show_completions(&ShowCompletions, window, cx);
16588    });
16589    cx.executor().run_until_parked();
16590    cx.update_editor(|editor, window, cx| {
16591        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16592    });
16593    cx.executor().run_until_parked();
16594    cx.assert_editor_state(
16595        r##"#ifndef BAR_H
16596#define BAR_H
16597
16598#include <stdbool.h>
16599
16600int fn_branch(bool do_branch1, bool do_branch2);
16601
16602#endif // BAR_H
16603#include "AGL/ˇ"##,
16604    );
16605
16606    cx.update_editor(|editor, window, cx| {
16607        editor.handle_input("\"", window, cx);
16608    });
16609    cx.executor().run_until_parked();
16610    cx.assert_editor_state(
16611        r##"#ifndef BAR_H
16612#define BAR_H
16613
16614#include <stdbool.h>
16615
16616int fn_branch(bool do_branch1, bool do_branch2);
16617
16618#endif // BAR_H
16619#include "AGL/"ˇ"##,
16620    );
16621}
16622
16623#[gpui::test]
16624async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
16625    init_test(cx, |_| {});
16626
16627    let mut cx = EditorLspTestContext::new_rust(
16628        lsp::ServerCapabilities {
16629            completion_provider: Some(lsp::CompletionOptions {
16630                trigger_characters: Some(vec![".".to_string()]),
16631                resolve_provider: Some(false),
16632                ..lsp::CompletionOptions::default()
16633            }),
16634            ..lsp::ServerCapabilities::default()
16635        },
16636        cx,
16637    )
16638    .await;
16639
16640    cx.set_state("fn main() { let a = 2ˇ; }");
16641    cx.simulate_keystroke(".");
16642    let completion_item = lsp::CompletionItem {
16643        label: "Some".into(),
16644        kind: Some(lsp::CompletionItemKind::SNIPPET),
16645        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16646        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16647            kind: lsp::MarkupKind::Markdown,
16648            value: "```rust\nSome(2)\n```".to_string(),
16649        })),
16650        deprecated: Some(false),
16651        sort_text: Some("Some".to_string()),
16652        filter_text: Some("Some".to_string()),
16653        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16654        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16655            range: lsp::Range {
16656                start: lsp::Position {
16657                    line: 0,
16658                    character: 22,
16659                },
16660                end: lsp::Position {
16661                    line: 0,
16662                    character: 22,
16663                },
16664            },
16665            new_text: "Some(2)".to_string(),
16666        })),
16667        additional_text_edits: Some(vec![lsp::TextEdit {
16668            range: lsp::Range {
16669                start: lsp::Position {
16670                    line: 0,
16671                    character: 20,
16672                },
16673                end: lsp::Position {
16674                    line: 0,
16675                    character: 22,
16676                },
16677            },
16678            new_text: "".to_string(),
16679        }]),
16680        ..Default::default()
16681    };
16682
16683    let closure_completion_item = completion_item.clone();
16684    let counter = Arc::new(AtomicUsize::new(0));
16685    let counter_clone = counter.clone();
16686    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16687        let task_completion_item = closure_completion_item.clone();
16688        counter_clone.fetch_add(1, atomic::Ordering::Release);
16689        async move {
16690            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16691                is_incomplete: true,
16692                item_defaults: None,
16693                items: vec![task_completion_item],
16694            })))
16695        }
16696    });
16697
16698    cx.condition(|editor, _| editor.context_menu_visible())
16699        .await;
16700    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16701    assert!(request.next().await.is_some());
16702    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16703
16704    cx.simulate_keystrokes("S o m");
16705    cx.condition(|editor, _| editor.context_menu_visible())
16706        .await;
16707    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16708    assert!(request.next().await.is_some());
16709    assert!(request.next().await.is_some());
16710    assert!(request.next().await.is_some());
16711    request.close();
16712    assert!(request.next().await.is_none());
16713    assert_eq!(
16714        counter.load(atomic::Ordering::Acquire),
16715        4,
16716        "With the completions menu open, only one LSP request should happen per input"
16717    );
16718}
16719
16720#[gpui::test]
16721async fn test_toggle_comment(cx: &mut TestAppContext) {
16722    init_test(cx, |_| {});
16723    let mut cx = EditorTestContext::new(cx).await;
16724    let language = Arc::new(Language::new(
16725        LanguageConfig {
16726            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16727            ..Default::default()
16728        },
16729        Some(tree_sitter_rust::LANGUAGE.into()),
16730    ));
16731    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16732
16733    // If multiple selections intersect a line, the line is only toggled once.
16734    cx.set_state(indoc! {"
16735        fn a() {
16736            «//b();
16737            ˇ»// «c();
16738            //ˇ»  d();
16739        }
16740    "});
16741
16742    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16743
16744    cx.assert_editor_state(indoc! {"
16745        fn a() {
16746            «b();
16747            ˇ»«c();
16748            ˇ» d();
16749        }
16750    "});
16751
16752    // The comment prefix is inserted at the same column for every line in a
16753    // selection.
16754    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16755
16756    cx.assert_editor_state(indoc! {"
16757        fn a() {
16758            // «b();
16759            ˇ»// «c();
16760            ˇ» // d();
16761        }
16762    "});
16763
16764    // If a selection ends at the beginning of a line, that line is not toggled.
16765    cx.set_selections_state(indoc! {"
16766        fn a() {
16767            // b();
16768            «// c();
16769        ˇ»     // d();
16770        }
16771    "});
16772
16773    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16774
16775    cx.assert_editor_state(indoc! {"
16776        fn a() {
16777            // b();
16778            «c();
16779        ˇ»     // d();
16780        }
16781    "});
16782
16783    // If a selection span a single line and is empty, the line is toggled.
16784    cx.set_state(indoc! {"
16785        fn a() {
16786            a();
16787            b();
16788        ˇ
16789        }
16790    "});
16791
16792    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16793
16794    cx.assert_editor_state(indoc! {"
16795        fn a() {
16796            a();
16797            b();
16798        //•ˇ
16799        }
16800    "});
16801
16802    // If a selection span multiple lines, empty lines are not toggled.
16803    cx.set_state(indoc! {"
16804        fn a() {
16805            «a();
16806
16807            c();ˇ»
16808        }
16809    "});
16810
16811    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16812
16813    cx.assert_editor_state(indoc! {"
16814        fn a() {
16815            // «a();
16816
16817            // c();ˇ»
16818        }
16819    "});
16820
16821    // If a selection includes multiple comment prefixes, all lines are uncommented.
16822    cx.set_state(indoc! {"
16823        fn a() {
16824            «// a();
16825            /// b();
16826            //! c();ˇ»
16827        }
16828    "});
16829
16830    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16831
16832    cx.assert_editor_state(indoc! {"
16833        fn a() {
16834            «a();
16835            b();
16836            c();ˇ»
16837        }
16838    "});
16839}
16840
16841#[gpui::test]
16842async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16843    init_test(cx, |_| {});
16844    let mut cx = EditorTestContext::new(cx).await;
16845    let language = Arc::new(Language::new(
16846        LanguageConfig {
16847            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16848            ..Default::default()
16849        },
16850        Some(tree_sitter_rust::LANGUAGE.into()),
16851    ));
16852    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16853
16854    let toggle_comments = &ToggleComments {
16855        advance_downwards: false,
16856        ignore_indent: true,
16857    };
16858
16859    // If multiple selections intersect a line, the line is only toggled once.
16860    cx.set_state(indoc! {"
16861        fn a() {
16862        //    «b();
16863        //    c();
16864        //    ˇ» d();
16865        }
16866    "});
16867
16868    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16869
16870    cx.assert_editor_state(indoc! {"
16871        fn a() {
16872            «b();
16873            c();
16874            ˇ» d();
16875        }
16876    "});
16877
16878    // The comment prefix is inserted at the beginning of each line
16879    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16880
16881    cx.assert_editor_state(indoc! {"
16882        fn a() {
16883        //    «b();
16884        //    c();
16885        //    ˇ» d();
16886        }
16887    "});
16888
16889    // If a selection ends at the beginning of a line, that line is not toggled.
16890    cx.set_selections_state(indoc! {"
16891        fn a() {
16892        //    b();
16893        //    «c();
16894        ˇ»//     d();
16895        }
16896    "});
16897
16898    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16899
16900    cx.assert_editor_state(indoc! {"
16901        fn a() {
16902        //    b();
16903            «c();
16904        ˇ»//     d();
16905        }
16906    "});
16907
16908    // If a selection span a single line and is empty, the line is toggled.
16909    cx.set_state(indoc! {"
16910        fn a() {
16911            a();
16912            b();
16913        ˇ
16914        }
16915    "});
16916
16917    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16918
16919    cx.assert_editor_state(indoc! {"
16920        fn a() {
16921            a();
16922            b();
16923        //ˇ
16924        }
16925    "});
16926
16927    // If a selection span multiple lines, empty lines are not toggled.
16928    cx.set_state(indoc! {"
16929        fn a() {
16930            «a();
16931
16932            c();ˇ»
16933        }
16934    "});
16935
16936    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16937
16938    cx.assert_editor_state(indoc! {"
16939        fn a() {
16940        //    «a();
16941
16942        //    c();ˇ»
16943        }
16944    "});
16945
16946    // If a selection includes multiple comment prefixes, all lines are uncommented.
16947    cx.set_state(indoc! {"
16948        fn a() {
16949        //    «a();
16950        ///    b();
16951        //!    c();ˇ»
16952        }
16953    "});
16954
16955    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16956
16957    cx.assert_editor_state(indoc! {"
16958        fn a() {
16959            «a();
16960            b();
16961            c();ˇ»
16962        }
16963    "});
16964}
16965
16966#[gpui::test]
16967async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16968    init_test(cx, |_| {});
16969
16970    let language = Arc::new(Language::new(
16971        LanguageConfig {
16972            line_comments: vec!["// ".into()],
16973            ..Default::default()
16974        },
16975        Some(tree_sitter_rust::LANGUAGE.into()),
16976    ));
16977
16978    let mut cx = EditorTestContext::new(cx).await;
16979
16980    cx.language_registry().add(language.clone());
16981    cx.update_buffer(|buffer, cx| {
16982        buffer.set_language(Some(language), cx);
16983    });
16984
16985    let toggle_comments = &ToggleComments {
16986        advance_downwards: true,
16987        ignore_indent: false,
16988    };
16989
16990    // Single cursor on one line -> advance
16991    // Cursor moves horizontally 3 characters as well on non-blank line
16992    cx.set_state(indoc!(
16993        "fn a() {
16994             ˇdog();
16995             cat();
16996        }"
16997    ));
16998    cx.update_editor(|editor, window, cx| {
16999        editor.toggle_comments(toggle_comments, window, cx);
17000    });
17001    cx.assert_editor_state(indoc!(
17002        "fn a() {
17003             // dog();
17004             catˇ();
17005        }"
17006    ));
17007
17008    // Single selection on one line -> don't advance
17009    cx.set_state(indoc!(
17010        "fn a() {
17011             «dog()ˇ»;
17012             cat();
17013        }"
17014    ));
17015    cx.update_editor(|editor, window, cx| {
17016        editor.toggle_comments(toggle_comments, window, cx);
17017    });
17018    cx.assert_editor_state(indoc!(
17019        "fn a() {
17020             // «dog()ˇ»;
17021             cat();
17022        }"
17023    ));
17024
17025    // Multiple cursors on one line -> advance
17026    cx.set_state(indoc!(
17027        "fn a() {
17028             ˇdˇog();
17029             cat();
17030        }"
17031    ));
17032    cx.update_editor(|editor, window, cx| {
17033        editor.toggle_comments(toggle_comments, window, cx);
17034    });
17035    cx.assert_editor_state(indoc!(
17036        "fn a() {
17037             // dog();
17038             catˇ(ˇ);
17039        }"
17040    ));
17041
17042    // Multiple cursors on one line, with selection -> don't advance
17043    cx.set_state(indoc!(
17044        "fn a() {
17045             ˇdˇog«()ˇ»;
17046             cat();
17047        }"
17048    ));
17049    cx.update_editor(|editor, window, cx| {
17050        editor.toggle_comments(toggle_comments, window, cx);
17051    });
17052    cx.assert_editor_state(indoc!(
17053        "fn a() {
17054             // ˇdˇog«()ˇ»;
17055             cat();
17056        }"
17057    ));
17058
17059    // Single cursor on one line -> advance
17060    // Cursor moves to column 0 on blank line
17061    cx.set_state(indoc!(
17062        "fn a() {
17063             ˇdog();
17064
17065             cat();
17066        }"
17067    ));
17068    cx.update_editor(|editor, window, cx| {
17069        editor.toggle_comments(toggle_comments, window, cx);
17070    });
17071    cx.assert_editor_state(indoc!(
17072        "fn a() {
17073             // dog();
17074        ˇ
17075             cat();
17076        }"
17077    ));
17078
17079    // Single cursor on one line -> advance
17080    // Cursor starts and ends at column 0
17081    cx.set_state(indoc!(
17082        "fn a() {
17083         ˇ    dog();
17084             cat();
17085        }"
17086    ));
17087    cx.update_editor(|editor, window, cx| {
17088        editor.toggle_comments(toggle_comments, window, cx);
17089    });
17090    cx.assert_editor_state(indoc!(
17091        "fn a() {
17092             // dog();
17093         ˇ    cat();
17094        }"
17095    ));
17096}
17097
17098#[gpui::test]
17099async fn test_toggle_block_comment(cx: &mut TestAppContext) {
17100    init_test(cx, |_| {});
17101
17102    let mut cx = EditorTestContext::new(cx).await;
17103
17104    let html_language = Arc::new(
17105        Language::new(
17106            LanguageConfig {
17107                name: "HTML".into(),
17108                block_comment: Some(BlockCommentConfig {
17109                    start: "<!-- ".into(),
17110                    prefix: "".into(),
17111                    end: " -->".into(),
17112                    tab_size: 0,
17113                }),
17114                ..Default::default()
17115            },
17116            Some(tree_sitter_html::LANGUAGE.into()),
17117        )
17118        .with_injection_query(
17119            r#"
17120            (script_element
17121                (raw_text) @injection.content
17122                (#set! injection.language "javascript"))
17123            "#,
17124        )
17125        .unwrap(),
17126    );
17127
17128    let javascript_language = Arc::new(Language::new(
17129        LanguageConfig {
17130            name: "JavaScript".into(),
17131            line_comments: vec!["// ".into()],
17132            ..Default::default()
17133        },
17134        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17135    ));
17136
17137    cx.language_registry().add(html_language.clone());
17138    cx.language_registry().add(javascript_language);
17139    cx.update_buffer(|buffer, cx| {
17140        buffer.set_language(Some(html_language), cx);
17141    });
17142
17143    // Toggle comments for empty selections
17144    cx.set_state(
17145        &r#"
17146            <p>A</p>ˇ
17147            <p>B</p>ˇ
17148            <p>C</p>ˇ
17149        "#
17150        .unindent(),
17151    );
17152    cx.update_editor(|editor, window, cx| {
17153        editor.toggle_comments(&ToggleComments::default(), window, cx)
17154    });
17155    cx.assert_editor_state(
17156        &r#"
17157            <!-- <p>A</p>ˇ -->
17158            <!-- <p>B</p>ˇ -->
17159            <!-- <p>C</p>ˇ -->
17160        "#
17161        .unindent(),
17162    );
17163    cx.update_editor(|editor, window, cx| {
17164        editor.toggle_comments(&ToggleComments::default(), window, cx)
17165    });
17166    cx.assert_editor_state(
17167        &r#"
17168            <p>A</p>ˇ
17169            <p>B</p>ˇ
17170            <p>C</p>ˇ
17171        "#
17172        .unindent(),
17173    );
17174
17175    // Toggle comments for mixture of empty and non-empty selections, where
17176    // multiple selections occupy a given line.
17177    cx.set_state(
17178        &r#"
17179            <p>A«</p>
17180            <p>ˇ»B</p>ˇ
17181            <p>C«</p>
17182            <p>ˇ»D</p>ˇ
17183        "#
17184        .unindent(),
17185    );
17186
17187    cx.update_editor(|editor, window, cx| {
17188        editor.toggle_comments(&ToggleComments::default(), window, cx)
17189    });
17190    cx.assert_editor_state(
17191        &r#"
17192            <!-- <p>A«</p>
17193            <p>ˇ»B</p>ˇ -->
17194            <!-- <p>C«</p>
17195            <p>ˇ»D</p>ˇ -->
17196        "#
17197        .unindent(),
17198    );
17199    cx.update_editor(|editor, window, cx| {
17200        editor.toggle_comments(&ToggleComments::default(), window, cx)
17201    });
17202    cx.assert_editor_state(
17203        &r#"
17204            <p>A«</p>
17205            <p>ˇ»B</p>ˇ
17206            <p>C«</p>
17207            <p>ˇ»D</p>ˇ
17208        "#
17209        .unindent(),
17210    );
17211
17212    // Toggle comments when different languages are active for different
17213    // selections.
17214    cx.set_state(
17215        &r#"
17216            ˇ<script>
17217                ˇvar x = new Y();
17218            ˇ</script>
17219        "#
17220        .unindent(),
17221    );
17222    cx.executor().run_until_parked();
17223    cx.update_editor(|editor, window, cx| {
17224        editor.toggle_comments(&ToggleComments::default(), window, cx)
17225    });
17226    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
17227    // Uncommenting and commenting from this position brings in even more wrong artifacts.
17228    cx.assert_editor_state(
17229        &r#"
17230            <!-- ˇ<script> -->
17231                // ˇvar x = new Y();
17232            <!-- ˇ</script> -->
17233        "#
17234        .unindent(),
17235    );
17236}
17237
17238#[gpui::test]
17239fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
17240    init_test(cx, |_| {});
17241
17242    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17243    let multibuffer = cx.new(|cx| {
17244        let mut multibuffer = MultiBuffer::new(ReadWrite);
17245        multibuffer.push_excerpts(
17246            buffer.clone(),
17247            [
17248                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
17249                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
17250            ],
17251            cx,
17252        );
17253        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
17254        multibuffer
17255    });
17256
17257    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
17258    editor.update_in(cx, |editor, window, cx| {
17259        assert_eq!(editor.text(cx), "aaaa\nbbbb");
17260        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17261            s.select_ranges([
17262                Point::new(0, 0)..Point::new(0, 0),
17263                Point::new(1, 0)..Point::new(1, 0),
17264            ])
17265        });
17266
17267        editor.handle_input("X", window, cx);
17268        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
17269        assert_eq!(
17270            editor.selections.ranges(&editor.display_snapshot(cx)),
17271            [
17272                Point::new(0, 1)..Point::new(0, 1),
17273                Point::new(1, 1)..Point::new(1, 1),
17274            ]
17275        );
17276
17277        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
17278        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17279            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
17280        });
17281        editor.backspace(&Default::default(), window, cx);
17282        assert_eq!(editor.text(cx), "Xa\nbbb");
17283        assert_eq!(
17284            editor.selections.ranges(&editor.display_snapshot(cx)),
17285            [Point::new(1, 0)..Point::new(1, 0)]
17286        );
17287
17288        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17289            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
17290        });
17291        editor.backspace(&Default::default(), window, cx);
17292        assert_eq!(editor.text(cx), "X\nbb");
17293        assert_eq!(
17294            editor.selections.ranges(&editor.display_snapshot(cx)),
17295            [Point::new(0, 1)..Point::new(0, 1)]
17296        );
17297    });
17298}
17299
17300#[gpui::test]
17301fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
17302    init_test(cx, |_| {});
17303
17304    let markers = vec![('[', ']').into(), ('(', ')').into()];
17305    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
17306        indoc! {"
17307            [aaaa
17308            (bbbb]
17309            cccc)",
17310        },
17311        markers.clone(),
17312    );
17313    let excerpt_ranges = markers.into_iter().map(|marker| {
17314        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
17315        ExcerptRange::new(context)
17316    });
17317    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
17318    let multibuffer = cx.new(|cx| {
17319        let mut multibuffer = MultiBuffer::new(ReadWrite);
17320        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
17321        multibuffer
17322    });
17323
17324    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
17325    editor.update_in(cx, |editor, window, cx| {
17326        let (expected_text, selection_ranges) = marked_text_ranges(
17327            indoc! {"
17328                aaaa
17329                bˇbbb
17330                bˇbbˇb
17331                cccc"
17332            },
17333            true,
17334        );
17335        assert_eq!(editor.text(cx), expected_text);
17336        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17337            s.select_ranges(
17338                selection_ranges
17339                    .iter()
17340                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
17341            )
17342        });
17343
17344        editor.handle_input("X", window, cx);
17345
17346        let (expected_text, expected_selections) = marked_text_ranges(
17347            indoc! {"
17348                aaaa
17349                bXˇbbXb
17350                bXˇbbXˇb
17351                cccc"
17352            },
17353            false,
17354        );
17355        assert_eq!(editor.text(cx), expected_text);
17356        assert_eq!(
17357            editor.selections.ranges(&editor.display_snapshot(cx)),
17358            expected_selections
17359                .iter()
17360                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17361                .collect::<Vec<_>>()
17362        );
17363
17364        editor.newline(&Newline, window, cx);
17365        let (expected_text, expected_selections) = marked_text_ranges(
17366            indoc! {"
17367                aaaa
17368                bX
17369                ˇbbX
17370                b
17371                bX
17372                ˇbbX
17373                ˇb
17374                cccc"
17375            },
17376            false,
17377        );
17378        assert_eq!(editor.text(cx), expected_text);
17379        assert_eq!(
17380            editor.selections.ranges(&editor.display_snapshot(cx)),
17381            expected_selections
17382                .iter()
17383                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17384                .collect::<Vec<_>>()
17385        );
17386    });
17387}
17388
17389#[gpui::test]
17390fn test_refresh_selections(cx: &mut TestAppContext) {
17391    init_test(cx, |_| {});
17392
17393    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17394    let mut excerpt1_id = None;
17395    let multibuffer = cx.new(|cx| {
17396        let mut multibuffer = MultiBuffer::new(ReadWrite);
17397        excerpt1_id = multibuffer
17398            .push_excerpts(
17399                buffer.clone(),
17400                [
17401                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17402                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17403                ],
17404                cx,
17405            )
17406            .into_iter()
17407            .next();
17408        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17409        multibuffer
17410    });
17411
17412    let editor = cx.add_window(|window, cx| {
17413        let mut editor = build_editor(multibuffer.clone(), window, cx);
17414        let snapshot = editor.snapshot(window, cx);
17415        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17416            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
17417        });
17418        editor.begin_selection(
17419            Point::new(2, 1).to_display_point(&snapshot),
17420            true,
17421            1,
17422            window,
17423            cx,
17424        );
17425        assert_eq!(
17426            editor.selections.ranges(&editor.display_snapshot(cx)),
17427            [
17428                Point::new(1, 3)..Point::new(1, 3),
17429                Point::new(2, 1)..Point::new(2, 1),
17430            ]
17431        );
17432        editor
17433    });
17434
17435    // Refreshing selections is a no-op when excerpts haven't changed.
17436    _ = editor.update(cx, |editor, window, cx| {
17437        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17438        assert_eq!(
17439            editor.selections.ranges(&editor.display_snapshot(cx)),
17440            [
17441                Point::new(1, 3)..Point::new(1, 3),
17442                Point::new(2, 1)..Point::new(2, 1),
17443            ]
17444        );
17445    });
17446
17447    multibuffer.update(cx, |multibuffer, cx| {
17448        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17449    });
17450    _ = editor.update(cx, |editor, window, cx| {
17451        // Removing an excerpt causes the first selection to become degenerate.
17452        assert_eq!(
17453            editor.selections.ranges(&editor.display_snapshot(cx)),
17454            [
17455                Point::new(0, 0)..Point::new(0, 0),
17456                Point::new(0, 1)..Point::new(0, 1)
17457            ]
17458        );
17459
17460        // Refreshing selections will relocate the first selection to the original buffer
17461        // location.
17462        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17463        assert_eq!(
17464            editor.selections.ranges(&editor.display_snapshot(cx)),
17465            [
17466                Point::new(0, 1)..Point::new(0, 1),
17467                Point::new(0, 3)..Point::new(0, 3)
17468            ]
17469        );
17470        assert!(editor.selections.pending_anchor().is_some());
17471    });
17472}
17473
17474#[gpui::test]
17475fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
17476    init_test(cx, |_| {});
17477
17478    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17479    let mut excerpt1_id = None;
17480    let multibuffer = cx.new(|cx| {
17481        let mut multibuffer = MultiBuffer::new(ReadWrite);
17482        excerpt1_id = multibuffer
17483            .push_excerpts(
17484                buffer.clone(),
17485                [
17486                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17487                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17488                ],
17489                cx,
17490            )
17491            .into_iter()
17492            .next();
17493        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17494        multibuffer
17495    });
17496
17497    let editor = cx.add_window(|window, cx| {
17498        let mut editor = build_editor(multibuffer.clone(), window, cx);
17499        let snapshot = editor.snapshot(window, cx);
17500        editor.begin_selection(
17501            Point::new(1, 3).to_display_point(&snapshot),
17502            false,
17503            1,
17504            window,
17505            cx,
17506        );
17507        assert_eq!(
17508            editor.selections.ranges(&editor.display_snapshot(cx)),
17509            [Point::new(1, 3)..Point::new(1, 3)]
17510        );
17511        editor
17512    });
17513
17514    multibuffer.update(cx, |multibuffer, cx| {
17515        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17516    });
17517    _ = editor.update(cx, |editor, window, cx| {
17518        assert_eq!(
17519            editor.selections.ranges(&editor.display_snapshot(cx)),
17520            [Point::new(0, 0)..Point::new(0, 0)]
17521        );
17522
17523        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
17524        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17525        assert_eq!(
17526            editor.selections.ranges(&editor.display_snapshot(cx)),
17527            [Point::new(0, 3)..Point::new(0, 3)]
17528        );
17529        assert!(editor.selections.pending_anchor().is_some());
17530    });
17531}
17532
17533#[gpui::test]
17534async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
17535    init_test(cx, |_| {});
17536
17537    let language = Arc::new(
17538        Language::new(
17539            LanguageConfig {
17540                brackets: BracketPairConfig {
17541                    pairs: vec![
17542                        BracketPair {
17543                            start: "{".to_string(),
17544                            end: "}".to_string(),
17545                            close: true,
17546                            surround: true,
17547                            newline: true,
17548                        },
17549                        BracketPair {
17550                            start: "/* ".to_string(),
17551                            end: " */".to_string(),
17552                            close: true,
17553                            surround: true,
17554                            newline: true,
17555                        },
17556                    ],
17557                    ..Default::default()
17558                },
17559                ..Default::default()
17560            },
17561            Some(tree_sitter_rust::LANGUAGE.into()),
17562        )
17563        .with_indents_query("")
17564        .unwrap(),
17565    );
17566
17567    let text = concat!(
17568        "{   }\n",     //
17569        "  x\n",       //
17570        "  /*   */\n", //
17571        "x\n",         //
17572        "{{} }\n",     //
17573    );
17574
17575    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17576    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17577    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17578    editor
17579        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
17580        .await;
17581
17582    editor.update_in(cx, |editor, window, cx| {
17583        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17584            s.select_display_ranges([
17585                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
17586                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
17587                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
17588            ])
17589        });
17590        editor.newline(&Newline, window, cx);
17591
17592        assert_eq!(
17593            editor.buffer().read(cx).read(cx).text(),
17594            concat!(
17595                "{ \n",    // Suppress rustfmt
17596                "\n",      //
17597                "}\n",     //
17598                "  x\n",   //
17599                "  /* \n", //
17600                "  \n",    //
17601                "  */\n",  //
17602                "x\n",     //
17603                "{{} \n",  //
17604                "}\n",     //
17605            )
17606        );
17607    });
17608}
17609
17610#[gpui::test]
17611fn test_highlighted_ranges(cx: &mut TestAppContext) {
17612    init_test(cx, |_| {});
17613
17614    let editor = cx.add_window(|window, cx| {
17615        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
17616        build_editor(buffer, window, cx)
17617    });
17618
17619    _ = editor.update(cx, |editor, window, cx| {
17620        let buffer = editor.buffer.read(cx).snapshot(cx);
17621
17622        let anchor_range =
17623            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
17624
17625        editor.highlight_background(
17626            HighlightKey::ColorizeBracket(0),
17627            &[
17628                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
17629                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
17630                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
17631                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
17632            ],
17633            |_, _| Hsla::red(),
17634            cx,
17635        );
17636        editor.highlight_background(
17637            HighlightKey::ColorizeBracket(1),
17638            &[
17639                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
17640                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
17641                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
17642                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
17643            ],
17644            |_, _| Hsla::green(),
17645            cx,
17646        );
17647
17648        let snapshot = editor.snapshot(window, cx);
17649        let highlighted_ranges = editor.sorted_background_highlights_in_range(
17650            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
17651            &snapshot,
17652            cx.theme(),
17653        );
17654        assert_eq!(
17655            highlighted_ranges,
17656            &[
17657                (
17658                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17659                    Hsla::green(),
17660                ),
17661                (
17662                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17663                    Hsla::red(),
17664                ),
17665                (
17666                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17667                    Hsla::green(),
17668                ),
17669                (
17670                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17671                    Hsla::red(),
17672                ),
17673            ]
17674        );
17675        assert_eq!(
17676            editor.sorted_background_highlights_in_range(
17677                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17678                &snapshot,
17679                cx.theme(),
17680            ),
17681            &[(
17682                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17683                Hsla::red(),
17684            )]
17685        );
17686    });
17687}
17688
17689#[gpui::test]
17690async fn test_following(cx: &mut TestAppContext) {
17691    init_test(cx, |_| {});
17692
17693    let fs = FakeFs::new(cx.executor());
17694    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17695
17696    let buffer = project.update(cx, |project, cx| {
17697        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17698        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17699    });
17700    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17701    let follower = cx.update(|cx| {
17702        cx.open_window(
17703            WindowOptions {
17704                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17705                    gpui::Point::new(px(0.), px(0.)),
17706                    gpui::Point::new(px(10.), px(80.)),
17707                ))),
17708                ..Default::default()
17709            },
17710            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17711        )
17712        .unwrap()
17713    });
17714
17715    let is_still_following = Rc::new(RefCell::new(true));
17716    let follower_edit_event_count = Rc::new(RefCell::new(0));
17717    let pending_update = Rc::new(RefCell::new(None));
17718    let leader_entity = leader.root(cx).unwrap();
17719    let follower_entity = follower.root(cx).unwrap();
17720    _ = follower.update(cx, {
17721        let update = pending_update.clone();
17722        let is_still_following = is_still_following.clone();
17723        let follower_edit_event_count = follower_edit_event_count.clone();
17724        |_, window, cx| {
17725            cx.subscribe_in(
17726                &leader_entity,
17727                window,
17728                move |_, leader, event, window, cx| {
17729                    leader.update(cx, |leader, cx| {
17730                        leader.add_event_to_update_proto(
17731                            event,
17732                            &mut update.borrow_mut(),
17733                            window,
17734                            cx,
17735                        );
17736                    });
17737                },
17738            )
17739            .detach();
17740
17741            cx.subscribe_in(
17742                &follower_entity,
17743                window,
17744                move |_, _, event: &EditorEvent, _window, _cx| {
17745                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17746                        *is_still_following.borrow_mut() = false;
17747                    }
17748
17749                    if let EditorEvent::BufferEdited = event {
17750                        *follower_edit_event_count.borrow_mut() += 1;
17751                    }
17752                },
17753            )
17754            .detach();
17755        }
17756    });
17757
17758    // Update the selections only
17759    _ = leader.update(cx, |leader, window, cx| {
17760        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17761            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17762        });
17763    });
17764    follower
17765        .update(cx, |follower, window, cx| {
17766            follower.apply_update_proto(
17767                &project,
17768                pending_update.borrow_mut().take().unwrap(),
17769                window,
17770                cx,
17771            )
17772        })
17773        .unwrap()
17774        .await
17775        .unwrap();
17776    _ = follower.update(cx, |follower, _, cx| {
17777        assert_eq!(
17778            follower.selections.ranges(&follower.display_snapshot(cx)),
17779            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17780        );
17781    });
17782    assert!(*is_still_following.borrow());
17783    assert_eq!(*follower_edit_event_count.borrow(), 0);
17784
17785    // Update the scroll position only
17786    _ = leader.update(cx, |leader, window, cx| {
17787        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17788    });
17789    follower
17790        .update(cx, |follower, window, cx| {
17791            follower.apply_update_proto(
17792                &project,
17793                pending_update.borrow_mut().take().unwrap(),
17794                window,
17795                cx,
17796            )
17797        })
17798        .unwrap()
17799        .await
17800        .unwrap();
17801    assert_eq!(
17802        follower
17803            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17804            .unwrap(),
17805        gpui::Point::new(1.5, 3.5)
17806    );
17807    assert!(*is_still_following.borrow());
17808    assert_eq!(*follower_edit_event_count.borrow(), 0);
17809
17810    // Update the selections and scroll position. The follower's scroll position is updated
17811    // via autoscroll, not via the leader's exact scroll position.
17812    _ = leader.update(cx, |leader, window, cx| {
17813        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17814            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17815        });
17816        leader.request_autoscroll(Autoscroll::newest(), cx);
17817        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17818    });
17819    follower
17820        .update(cx, |follower, window, cx| {
17821            follower.apply_update_proto(
17822                &project,
17823                pending_update.borrow_mut().take().unwrap(),
17824                window,
17825                cx,
17826            )
17827        })
17828        .unwrap()
17829        .await
17830        .unwrap();
17831    _ = follower.update(cx, |follower, _, cx| {
17832        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17833        assert_eq!(
17834            follower.selections.ranges(&follower.display_snapshot(cx)),
17835            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17836        );
17837    });
17838    assert!(*is_still_following.borrow());
17839
17840    // Creating a pending selection that precedes another selection
17841    _ = leader.update(cx, |leader, window, cx| {
17842        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17843            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17844        });
17845        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17846    });
17847    follower
17848        .update(cx, |follower, window, cx| {
17849            follower.apply_update_proto(
17850                &project,
17851                pending_update.borrow_mut().take().unwrap(),
17852                window,
17853                cx,
17854            )
17855        })
17856        .unwrap()
17857        .await
17858        .unwrap();
17859    _ = follower.update(cx, |follower, _, cx| {
17860        assert_eq!(
17861            follower.selections.ranges(&follower.display_snapshot(cx)),
17862            vec![
17863                MultiBufferOffset(0)..MultiBufferOffset(0),
17864                MultiBufferOffset(1)..MultiBufferOffset(1)
17865            ]
17866        );
17867    });
17868    assert!(*is_still_following.borrow());
17869
17870    // Extend the pending selection so that it surrounds another selection
17871    _ = leader.update(cx, |leader, window, cx| {
17872        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17873    });
17874    follower
17875        .update(cx, |follower, window, cx| {
17876            follower.apply_update_proto(
17877                &project,
17878                pending_update.borrow_mut().take().unwrap(),
17879                window,
17880                cx,
17881            )
17882        })
17883        .unwrap()
17884        .await
17885        .unwrap();
17886    _ = follower.update(cx, |follower, _, cx| {
17887        assert_eq!(
17888            follower.selections.ranges(&follower.display_snapshot(cx)),
17889            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17890        );
17891    });
17892
17893    // Scrolling locally breaks the follow
17894    _ = follower.update(cx, |follower, window, cx| {
17895        let top_anchor = follower
17896            .buffer()
17897            .read(cx)
17898            .read(cx)
17899            .anchor_after(MultiBufferOffset(0));
17900        follower.set_scroll_anchor(
17901            ScrollAnchor {
17902                anchor: top_anchor,
17903                offset: gpui::Point::new(0.0, 0.5),
17904            },
17905            window,
17906            cx,
17907        );
17908    });
17909    assert!(!(*is_still_following.borrow()));
17910}
17911
17912#[gpui::test]
17913async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17914    init_test(cx, |_| {});
17915
17916    let fs = FakeFs::new(cx.executor());
17917    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17918    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17919    let pane = workspace
17920        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17921        .unwrap();
17922
17923    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17924
17925    let leader = pane.update_in(cx, |_, window, cx| {
17926        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17927        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17928    });
17929
17930    // Start following the editor when it has no excerpts.
17931    let mut state_message =
17932        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17933    let workspace_entity = workspace.root(cx).unwrap();
17934    let follower_1 = cx
17935        .update_window(*workspace.deref(), |_, window, cx| {
17936            Editor::from_state_proto(
17937                workspace_entity,
17938                ViewId {
17939                    creator: CollaboratorId::PeerId(PeerId::default()),
17940                    id: 0,
17941                },
17942                &mut state_message,
17943                window,
17944                cx,
17945            )
17946        })
17947        .unwrap()
17948        .unwrap()
17949        .await
17950        .unwrap();
17951
17952    let update_message = Rc::new(RefCell::new(None));
17953    follower_1.update_in(cx, {
17954        let update = update_message.clone();
17955        |_, window, cx| {
17956            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17957                leader.update(cx, |leader, cx| {
17958                    leader.add_event_to_update_proto(event, &mut update.borrow_mut(), window, cx);
17959                });
17960            })
17961            .detach();
17962        }
17963    });
17964
17965    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17966        (
17967            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17968            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17969        )
17970    });
17971
17972    // Insert some excerpts.
17973    leader.update(cx, |leader, cx| {
17974        leader.buffer.update(cx, |multibuffer, cx| {
17975            multibuffer.set_excerpts_for_path(
17976                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17977                buffer_1.clone(),
17978                vec![
17979                    Point::row_range(0..3),
17980                    Point::row_range(1..6),
17981                    Point::row_range(12..15),
17982                ],
17983                0,
17984                cx,
17985            );
17986            multibuffer.set_excerpts_for_path(
17987                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17988                buffer_2.clone(),
17989                vec![Point::row_range(0..6), Point::row_range(8..12)],
17990                0,
17991                cx,
17992            );
17993        });
17994    });
17995
17996    // Apply the update of adding the excerpts.
17997    follower_1
17998        .update_in(cx, |follower, window, cx| {
17999            follower.apply_update_proto(
18000                &project,
18001                update_message.borrow().clone().unwrap(),
18002                window,
18003                cx,
18004            )
18005        })
18006        .await
18007        .unwrap();
18008    assert_eq!(
18009        follower_1.update(cx, |editor, cx| editor.text(cx)),
18010        leader.update(cx, |editor, cx| editor.text(cx))
18011    );
18012    update_message.borrow_mut().take();
18013
18014    // Start following separately after it already has excerpts.
18015    let mut state_message =
18016        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
18017    let workspace_entity = workspace.root(cx).unwrap();
18018    let follower_2 = cx
18019        .update_window(*workspace.deref(), |_, window, cx| {
18020            Editor::from_state_proto(
18021                workspace_entity,
18022                ViewId {
18023                    creator: CollaboratorId::PeerId(PeerId::default()),
18024                    id: 0,
18025                },
18026                &mut state_message,
18027                window,
18028                cx,
18029            )
18030        })
18031        .unwrap()
18032        .unwrap()
18033        .await
18034        .unwrap();
18035    assert_eq!(
18036        follower_2.update(cx, |editor, cx| editor.text(cx)),
18037        leader.update(cx, |editor, cx| editor.text(cx))
18038    );
18039
18040    // Remove some excerpts.
18041    leader.update(cx, |leader, cx| {
18042        leader.buffer.update(cx, |multibuffer, cx| {
18043            let excerpt_ids = multibuffer.excerpt_ids();
18044            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
18045            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
18046        });
18047    });
18048
18049    // Apply the update of removing the excerpts.
18050    follower_1
18051        .update_in(cx, |follower, window, cx| {
18052            follower.apply_update_proto(
18053                &project,
18054                update_message.borrow().clone().unwrap(),
18055                window,
18056                cx,
18057            )
18058        })
18059        .await
18060        .unwrap();
18061    follower_2
18062        .update_in(cx, |follower, window, cx| {
18063            follower.apply_update_proto(
18064                &project,
18065                update_message.borrow().clone().unwrap(),
18066                window,
18067                cx,
18068            )
18069        })
18070        .await
18071        .unwrap();
18072    update_message.borrow_mut().take();
18073    assert_eq!(
18074        follower_1.update(cx, |editor, cx| editor.text(cx)),
18075        leader.update(cx, |editor, cx| editor.text(cx))
18076    );
18077}
18078
18079#[gpui::test]
18080async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18081    init_test(cx, |_| {});
18082
18083    let mut cx = EditorTestContext::new(cx).await;
18084    let lsp_store =
18085        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
18086
18087    cx.set_state(indoc! {"
18088        ˇfn func(abc def: i32) -> u32 {
18089        }
18090    "});
18091
18092    cx.update(|_, cx| {
18093        lsp_store.update(cx, |lsp_store, cx| {
18094            lsp_store
18095                .update_diagnostics(
18096                    LanguageServerId(0),
18097                    lsp::PublishDiagnosticsParams {
18098                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
18099                        version: None,
18100                        diagnostics: vec![
18101                            lsp::Diagnostic {
18102                                range: lsp::Range::new(
18103                                    lsp::Position::new(0, 11),
18104                                    lsp::Position::new(0, 12),
18105                                ),
18106                                severity: Some(lsp::DiagnosticSeverity::ERROR),
18107                                ..Default::default()
18108                            },
18109                            lsp::Diagnostic {
18110                                range: lsp::Range::new(
18111                                    lsp::Position::new(0, 12),
18112                                    lsp::Position::new(0, 15),
18113                                ),
18114                                severity: Some(lsp::DiagnosticSeverity::ERROR),
18115                                ..Default::default()
18116                            },
18117                            lsp::Diagnostic {
18118                                range: lsp::Range::new(
18119                                    lsp::Position::new(0, 25),
18120                                    lsp::Position::new(0, 28),
18121                                ),
18122                                severity: Some(lsp::DiagnosticSeverity::ERROR),
18123                                ..Default::default()
18124                            },
18125                        ],
18126                    },
18127                    None,
18128                    DiagnosticSourceKind::Pushed,
18129                    &[],
18130                    cx,
18131                )
18132                .unwrap()
18133        });
18134    });
18135
18136    executor.run_until_parked();
18137
18138    cx.update_editor(|editor, window, cx| {
18139        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18140    });
18141
18142    cx.assert_editor_state(indoc! {"
18143        fn func(abc def: i32) -> ˇu32 {
18144        }
18145    "});
18146
18147    cx.update_editor(|editor, window, cx| {
18148        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18149    });
18150
18151    cx.assert_editor_state(indoc! {"
18152        fn func(abc ˇdef: i32) -> u32 {
18153        }
18154    "});
18155
18156    cx.update_editor(|editor, window, cx| {
18157        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18158    });
18159
18160    cx.assert_editor_state(indoc! {"
18161        fn func(abcˇ def: i32) -> u32 {
18162        }
18163    "});
18164
18165    cx.update_editor(|editor, window, cx| {
18166        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18167    });
18168
18169    cx.assert_editor_state(indoc! {"
18170        fn func(abc def: i32) -> ˇu32 {
18171        }
18172    "});
18173}
18174
18175#[gpui::test]
18176async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18177    init_test(cx, |_| {});
18178
18179    let mut cx = EditorTestContext::new(cx).await;
18180
18181    let diff_base = r#"
18182        use some::mod;
18183
18184        const A: u32 = 42;
18185
18186        fn main() {
18187            println!("hello");
18188
18189            println!("world");
18190        }
18191        "#
18192    .unindent();
18193
18194    // Edits are modified, removed, modified, added
18195    cx.set_state(
18196        &r#"
18197        use some::modified;
18198
18199        ˇ
18200        fn main() {
18201            println!("hello there");
18202
18203            println!("around the");
18204            println!("world");
18205        }
18206        "#
18207        .unindent(),
18208    );
18209
18210    cx.set_head_text(&diff_base);
18211    executor.run_until_parked();
18212
18213    cx.update_editor(|editor, window, cx| {
18214        //Wrap around the bottom of the buffer
18215        for _ in 0..3 {
18216            editor.go_to_next_hunk(&GoToHunk, window, cx);
18217        }
18218    });
18219
18220    cx.assert_editor_state(
18221        &r#"
18222        ˇuse some::modified;
18223
18224
18225        fn main() {
18226            println!("hello there");
18227
18228            println!("around the");
18229            println!("world");
18230        }
18231        "#
18232        .unindent(),
18233    );
18234
18235    cx.update_editor(|editor, window, cx| {
18236        //Wrap around the top of the buffer
18237        for _ in 0..2 {
18238            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18239        }
18240    });
18241
18242    cx.assert_editor_state(
18243        &r#"
18244        use some::modified;
18245
18246
18247        fn main() {
18248        ˇ    println!("hello there");
18249
18250            println!("around the");
18251            println!("world");
18252        }
18253        "#
18254        .unindent(),
18255    );
18256
18257    cx.update_editor(|editor, window, cx| {
18258        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18259    });
18260
18261    cx.assert_editor_state(
18262        &r#"
18263        use some::modified;
18264
18265        ˇ
18266        fn main() {
18267            println!("hello there");
18268
18269            println!("around the");
18270            println!("world");
18271        }
18272        "#
18273        .unindent(),
18274    );
18275
18276    cx.update_editor(|editor, window, cx| {
18277        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18278    });
18279
18280    cx.assert_editor_state(
18281        &r#"
18282        ˇuse some::modified;
18283
18284
18285        fn main() {
18286            println!("hello there");
18287
18288            println!("around the");
18289            println!("world");
18290        }
18291        "#
18292        .unindent(),
18293    );
18294
18295    cx.update_editor(|editor, window, cx| {
18296        for _ in 0..2 {
18297            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18298        }
18299    });
18300
18301    cx.assert_editor_state(
18302        &r#"
18303        use some::modified;
18304
18305
18306        fn main() {
18307        ˇ    println!("hello there");
18308
18309            println!("around the");
18310            println!("world");
18311        }
18312        "#
18313        .unindent(),
18314    );
18315
18316    cx.update_editor(|editor, window, cx| {
18317        editor.fold(&Fold, window, cx);
18318    });
18319
18320    cx.update_editor(|editor, window, cx| {
18321        editor.go_to_next_hunk(&GoToHunk, window, cx);
18322    });
18323
18324    cx.assert_editor_state(
18325        &r#"
18326        ˇuse some::modified;
18327
18328
18329        fn main() {
18330            println!("hello there");
18331
18332            println!("around the");
18333            println!("world");
18334        }
18335        "#
18336        .unindent(),
18337    );
18338}
18339
18340#[test]
18341fn test_split_words() {
18342    fn split(text: &str) -> Vec<&str> {
18343        split_words(text).collect()
18344    }
18345
18346    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
18347    assert_eq!(split("hello_world"), &["hello_", "world"]);
18348    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
18349    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
18350    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
18351    assert_eq!(split("helloworld"), &["helloworld"]);
18352
18353    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
18354}
18355
18356#[test]
18357fn test_split_words_for_snippet_prefix() {
18358    fn split(text: &str) -> Vec<&str> {
18359        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
18360    }
18361
18362    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
18363    assert_eq!(split("hello_world"), &["hello_world"]);
18364    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
18365    assert_eq!(split("Hello_World"), &["Hello_World"]);
18366    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
18367    assert_eq!(split("helloworld"), &["helloworld"]);
18368    assert_eq!(
18369        split("this@is!@#$^many   . symbols"),
18370        &[
18371            "symbols",
18372            " symbols",
18373            ". symbols",
18374            " . symbols",
18375            "  . symbols",
18376            "   . symbols",
18377            "many   . symbols",
18378            "^many   . symbols",
18379            "$^many   . symbols",
18380            "#$^many   . symbols",
18381            "@#$^many   . symbols",
18382            "!@#$^many   . symbols",
18383            "is!@#$^many   . symbols",
18384            "@is!@#$^many   . symbols",
18385            "this@is!@#$^many   . symbols",
18386        ],
18387    );
18388    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
18389}
18390
18391#[gpui::test]
18392async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
18393    init_test(cx, |_| {});
18394
18395    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
18396
18397    #[track_caller]
18398    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
18399        let _state_context = cx.set_state(before);
18400        cx.run_until_parked();
18401        cx.update_editor(|editor, window, cx| {
18402            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
18403        });
18404        cx.run_until_parked();
18405        cx.assert_editor_state(after);
18406    }
18407
18408    // Outside bracket jumps to outside of matching bracket
18409    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
18410    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
18411
18412    // Inside bracket jumps to inside of matching bracket
18413    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
18414    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
18415
18416    // When outside a bracket and inside, favor jumping to the inside bracket
18417    assert(
18418        "console.log('foo', [1, 2, 3]ˇ);",
18419        "console.log('foo', ˇ[1, 2, 3]);",
18420        &mut cx,
18421    );
18422    assert(
18423        "console.log(ˇ'foo', [1, 2, 3]);",
18424        "console.log('foo'ˇ, [1, 2, 3]);",
18425        &mut cx,
18426    );
18427
18428    // Bias forward if two options are equally likely
18429    assert(
18430        "let result = curried_fun()ˇ();",
18431        "let result = curried_fun()()ˇ;",
18432        &mut cx,
18433    );
18434
18435    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
18436    assert(
18437        indoc! {"
18438            function test() {
18439                console.log('test')ˇ
18440            }"},
18441        indoc! {"
18442            function test() {
18443                console.logˇ('test')
18444            }"},
18445        &mut cx,
18446    );
18447}
18448
18449#[gpui::test]
18450async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
18451    init_test(cx, |_| {});
18452    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
18453    language_registry.add(markdown_lang());
18454    language_registry.add(rust_lang());
18455    let buffer = cx.new(|cx| {
18456        let mut buffer = language::Buffer::local(
18457            indoc! {"
18458            ```rs
18459            impl Worktree {
18460                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18461                }
18462            }
18463            ```
18464        "},
18465            cx,
18466        );
18467        buffer.set_language_registry(language_registry.clone());
18468        buffer.set_language(Some(markdown_lang()), cx);
18469        buffer
18470    });
18471    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18472    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18473    cx.executor().run_until_parked();
18474    _ = editor.update(cx, |editor, window, cx| {
18475        // Case 1: Test outer enclosing brackets
18476        select_ranges(
18477            editor,
18478            &indoc! {"
18479                ```rs
18480                impl Worktree {
18481                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18482                    }
1848318484                ```
18485            "},
18486            window,
18487            cx,
18488        );
18489        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18490        assert_text_with_selections(
18491            editor,
18492            &indoc! {"
18493                ```rs
18494                impl Worktree ˇ{
18495                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18496                    }
18497                }
18498                ```
18499            "},
18500            cx,
18501        );
18502        // Case 2: Test inner enclosing brackets
18503        select_ranges(
18504            editor,
18505            &indoc! {"
18506                ```rs
18507                impl Worktree {
18508                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1850918510                }
18511                ```
18512            "},
18513            window,
18514            cx,
18515        );
18516        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18517        assert_text_with_selections(
18518            editor,
18519            &indoc! {"
18520                ```rs
18521                impl Worktree {
18522                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
18523                    }
18524                }
18525                ```
18526            "},
18527            cx,
18528        );
18529    });
18530}
18531
18532#[gpui::test]
18533async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
18534    init_test(cx, |_| {});
18535
18536    let fs = FakeFs::new(cx.executor());
18537    fs.insert_tree(
18538        path!("/a"),
18539        json!({
18540            "main.rs": "fn main() { let a = 5; }",
18541            "other.rs": "// Test file",
18542        }),
18543    )
18544    .await;
18545    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18546
18547    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18548    language_registry.add(Arc::new(Language::new(
18549        LanguageConfig {
18550            name: "Rust".into(),
18551            matcher: LanguageMatcher {
18552                path_suffixes: vec!["rs".to_string()],
18553                ..Default::default()
18554            },
18555            brackets: BracketPairConfig {
18556                pairs: vec![BracketPair {
18557                    start: "{".to_string(),
18558                    end: "}".to_string(),
18559                    close: true,
18560                    surround: true,
18561                    newline: true,
18562                }],
18563                disabled_scopes_by_bracket_ix: Vec::new(),
18564            },
18565            ..Default::default()
18566        },
18567        Some(tree_sitter_rust::LANGUAGE.into()),
18568    )));
18569    let mut fake_servers = language_registry.register_fake_lsp(
18570        "Rust",
18571        FakeLspAdapter {
18572            capabilities: lsp::ServerCapabilities {
18573                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18574                    first_trigger_character: "{".to_string(),
18575                    more_trigger_character: None,
18576                }),
18577                ..Default::default()
18578            },
18579            ..Default::default()
18580        },
18581    );
18582
18583    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18584
18585    let cx = &mut VisualTestContext::from_window(*workspace, cx);
18586
18587    let worktree_id = workspace
18588        .update(cx, |workspace, _, cx| {
18589            workspace.project().update(cx, |project, cx| {
18590                project.worktrees(cx).next().unwrap().read(cx).id()
18591            })
18592        })
18593        .unwrap();
18594
18595    let buffer = project
18596        .update(cx, |project, cx| {
18597            project.open_local_buffer(path!("/a/main.rs"), cx)
18598        })
18599        .await
18600        .unwrap();
18601    let editor_handle = workspace
18602        .update(cx, |workspace, window, cx| {
18603            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
18604        })
18605        .unwrap()
18606        .await
18607        .unwrap()
18608        .downcast::<Editor>()
18609        .unwrap();
18610
18611    let fake_server = fake_servers.next().await.unwrap();
18612
18613    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
18614        |params, _| async move {
18615            assert_eq!(
18616                params.text_document_position.text_document.uri,
18617                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
18618            );
18619            assert_eq!(
18620                params.text_document_position.position,
18621                lsp::Position::new(0, 21),
18622            );
18623
18624            Ok(Some(vec![lsp::TextEdit {
18625                new_text: "]".to_string(),
18626                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18627            }]))
18628        },
18629    );
18630
18631    editor_handle.update_in(cx, |editor, window, cx| {
18632        window.focus(&editor.focus_handle(cx), cx);
18633        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18634            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
18635        });
18636        editor.handle_input("{", window, cx);
18637    });
18638
18639    cx.executor().run_until_parked();
18640
18641    buffer.update(cx, |buffer, _| {
18642        assert_eq!(
18643            buffer.text(),
18644            "fn main() { let a = {5}; }",
18645            "No extra braces from on type formatting should appear in the buffer"
18646        )
18647    });
18648}
18649
18650#[gpui::test(iterations = 20, seeds(31))]
18651async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18652    init_test(cx, |_| {});
18653
18654    let mut cx = EditorLspTestContext::new_rust(
18655        lsp::ServerCapabilities {
18656            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18657                first_trigger_character: ".".to_string(),
18658                more_trigger_character: None,
18659            }),
18660            ..Default::default()
18661        },
18662        cx,
18663    )
18664    .await;
18665
18666    cx.update_buffer(|buffer, _| {
18667        // This causes autoindent to be async.
18668        buffer.set_sync_parse_timeout(None)
18669    });
18670
18671    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18672    cx.simulate_keystroke("\n");
18673    cx.run_until_parked();
18674
18675    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18676    let mut request =
18677        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18678            let buffer_cloned = buffer_cloned.clone();
18679            async move {
18680                buffer_cloned.update(&mut cx, |buffer, _| {
18681                    assert_eq!(
18682                        buffer.text(),
18683                        "fn c() {\n    d()\n        .\n}\n",
18684                        "OnTypeFormatting should triggered after autoindent applied"
18685                    )
18686                });
18687
18688                Ok(Some(vec![]))
18689            }
18690        });
18691
18692    cx.simulate_keystroke(".");
18693    cx.run_until_parked();
18694
18695    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18696    assert!(request.next().await.is_some());
18697    request.close();
18698    assert!(request.next().await.is_none());
18699}
18700
18701#[gpui::test]
18702async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18703    init_test(cx, |_| {});
18704
18705    let fs = FakeFs::new(cx.executor());
18706    fs.insert_tree(
18707        path!("/a"),
18708        json!({
18709            "main.rs": "fn main() { let a = 5; }",
18710            "other.rs": "// Test file",
18711        }),
18712    )
18713    .await;
18714
18715    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18716
18717    let server_restarts = Arc::new(AtomicUsize::new(0));
18718    let closure_restarts = Arc::clone(&server_restarts);
18719    let language_server_name = "test language server";
18720    let language_name: LanguageName = "Rust".into();
18721
18722    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18723    language_registry.add(Arc::new(Language::new(
18724        LanguageConfig {
18725            name: language_name.clone(),
18726            matcher: LanguageMatcher {
18727                path_suffixes: vec!["rs".to_string()],
18728                ..Default::default()
18729            },
18730            ..Default::default()
18731        },
18732        Some(tree_sitter_rust::LANGUAGE.into()),
18733    )));
18734    let mut fake_servers = language_registry.register_fake_lsp(
18735        "Rust",
18736        FakeLspAdapter {
18737            name: language_server_name,
18738            initialization_options: Some(json!({
18739                "testOptionValue": true
18740            })),
18741            initializer: Some(Box::new(move |fake_server| {
18742                let task_restarts = Arc::clone(&closure_restarts);
18743                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18744                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18745                    futures::future::ready(Ok(()))
18746                });
18747            })),
18748            ..Default::default()
18749        },
18750    );
18751
18752    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18753    let _buffer = project
18754        .update(cx, |project, cx| {
18755            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18756        })
18757        .await
18758        .unwrap();
18759    let _fake_server = fake_servers.next().await.unwrap();
18760    update_test_language_settings(cx, |language_settings| {
18761        language_settings.languages.0.insert(
18762            language_name.clone().0.to_string(),
18763            LanguageSettingsContent {
18764                tab_size: NonZeroU32::new(8),
18765                ..Default::default()
18766            },
18767        );
18768    });
18769    cx.executor().run_until_parked();
18770    assert_eq!(
18771        server_restarts.load(atomic::Ordering::Acquire),
18772        0,
18773        "Should not restart LSP server on an unrelated change"
18774    );
18775
18776    update_test_project_settings(cx, |project_settings| {
18777        project_settings.lsp.0.insert(
18778            "Some other server name".into(),
18779            LspSettings {
18780                binary: None,
18781                settings: None,
18782                initialization_options: Some(json!({
18783                    "some other init value": false
18784                })),
18785                enable_lsp_tasks: false,
18786                fetch: None,
18787            },
18788        );
18789    });
18790    cx.executor().run_until_parked();
18791    assert_eq!(
18792        server_restarts.load(atomic::Ordering::Acquire),
18793        0,
18794        "Should not restart LSP server on an unrelated LSP settings change"
18795    );
18796
18797    update_test_project_settings(cx, |project_settings| {
18798        project_settings.lsp.0.insert(
18799            language_server_name.into(),
18800            LspSettings {
18801                binary: None,
18802                settings: None,
18803                initialization_options: Some(json!({
18804                    "anotherInitValue": false
18805                })),
18806                enable_lsp_tasks: false,
18807                fetch: None,
18808            },
18809        );
18810    });
18811    cx.executor().run_until_parked();
18812    assert_eq!(
18813        server_restarts.load(atomic::Ordering::Acquire),
18814        1,
18815        "Should restart LSP server on a related LSP settings change"
18816    );
18817
18818    update_test_project_settings(cx, |project_settings| {
18819        project_settings.lsp.0.insert(
18820            language_server_name.into(),
18821            LspSettings {
18822                binary: None,
18823                settings: None,
18824                initialization_options: Some(json!({
18825                    "anotherInitValue": false
18826                })),
18827                enable_lsp_tasks: false,
18828                fetch: None,
18829            },
18830        );
18831    });
18832    cx.executor().run_until_parked();
18833    assert_eq!(
18834        server_restarts.load(atomic::Ordering::Acquire),
18835        1,
18836        "Should not restart LSP server on a related LSP settings change that is the same"
18837    );
18838
18839    update_test_project_settings(cx, |project_settings| {
18840        project_settings.lsp.0.insert(
18841            language_server_name.into(),
18842            LspSettings {
18843                binary: None,
18844                settings: None,
18845                initialization_options: None,
18846                enable_lsp_tasks: false,
18847                fetch: None,
18848            },
18849        );
18850    });
18851    cx.executor().run_until_parked();
18852    assert_eq!(
18853        server_restarts.load(atomic::Ordering::Acquire),
18854        2,
18855        "Should restart LSP server on another related LSP settings change"
18856    );
18857}
18858
18859#[gpui::test]
18860async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18861    init_test(cx, |_| {});
18862
18863    let mut cx = EditorLspTestContext::new_rust(
18864        lsp::ServerCapabilities {
18865            completion_provider: Some(lsp::CompletionOptions {
18866                trigger_characters: Some(vec![".".to_string()]),
18867                resolve_provider: Some(true),
18868                ..Default::default()
18869            }),
18870            ..Default::default()
18871        },
18872        cx,
18873    )
18874    .await;
18875
18876    cx.set_state("fn main() { let a = 2ˇ; }");
18877    cx.simulate_keystroke(".");
18878    let completion_item = lsp::CompletionItem {
18879        label: "some".into(),
18880        kind: Some(lsp::CompletionItemKind::SNIPPET),
18881        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18882        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18883            kind: lsp::MarkupKind::Markdown,
18884            value: "```rust\nSome(2)\n```".to_string(),
18885        })),
18886        deprecated: Some(false),
18887        sort_text: Some("fffffff2".to_string()),
18888        filter_text: Some("some".to_string()),
18889        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18890        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18891            range: lsp::Range {
18892                start: lsp::Position {
18893                    line: 0,
18894                    character: 22,
18895                },
18896                end: lsp::Position {
18897                    line: 0,
18898                    character: 22,
18899                },
18900            },
18901            new_text: "Some(2)".to_string(),
18902        })),
18903        additional_text_edits: Some(vec![lsp::TextEdit {
18904            range: lsp::Range {
18905                start: lsp::Position {
18906                    line: 0,
18907                    character: 20,
18908                },
18909                end: lsp::Position {
18910                    line: 0,
18911                    character: 22,
18912                },
18913            },
18914            new_text: "".to_string(),
18915        }]),
18916        ..Default::default()
18917    };
18918
18919    let closure_completion_item = completion_item.clone();
18920    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18921        let task_completion_item = closure_completion_item.clone();
18922        async move {
18923            Ok(Some(lsp::CompletionResponse::Array(vec![
18924                task_completion_item,
18925            ])))
18926        }
18927    });
18928
18929    request.next().await;
18930
18931    cx.condition(|editor, _| editor.context_menu_visible())
18932        .await;
18933    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18934        editor
18935            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18936            .unwrap()
18937    });
18938    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18939
18940    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18941        let task_completion_item = completion_item.clone();
18942        async move { Ok(task_completion_item) }
18943    })
18944    .next()
18945    .await
18946    .unwrap();
18947    apply_additional_edits.await.unwrap();
18948    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18949}
18950
18951#[gpui::test]
18952async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18953    init_test(cx, |_| {});
18954
18955    let mut cx = EditorLspTestContext::new_rust(
18956        lsp::ServerCapabilities {
18957            completion_provider: Some(lsp::CompletionOptions {
18958                trigger_characters: Some(vec![".".to_string()]),
18959                resolve_provider: Some(true),
18960                ..Default::default()
18961            }),
18962            ..Default::default()
18963        },
18964        cx,
18965    )
18966    .await;
18967
18968    cx.set_state("fn main() { let a = 2ˇ; }");
18969    cx.simulate_keystroke(".");
18970
18971    let item1 = lsp::CompletionItem {
18972        label: "method id()".to_string(),
18973        filter_text: Some("id".to_string()),
18974        detail: None,
18975        documentation: None,
18976        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18977            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18978            new_text: ".id".to_string(),
18979        })),
18980        ..lsp::CompletionItem::default()
18981    };
18982
18983    let item2 = lsp::CompletionItem {
18984        label: "other".to_string(),
18985        filter_text: Some("other".to_string()),
18986        detail: None,
18987        documentation: None,
18988        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18989            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18990            new_text: ".other".to_string(),
18991        })),
18992        ..lsp::CompletionItem::default()
18993    };
18994
18995    let item1 = item1.clone();
18996    cx.set_request_handler::<lsp::request::Completion, _, _>({
18997        let item1 = item1.clone();
18998        move |_, _, _| {
18999            let item1 = item1.clone();
19000            let item2 = item2.clone();
19001            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
19002        }
19003    })
19004    .next()
19005    .await;
19006
19007    cx.condition(|editor, _| editor.context_menu_visible())
19008        .await;
19009    cx.update_editor(|editor, _, _| {
19010        let context_menu = editor.context_menu.borrow_mut();
19011        let context_menu = context_menu
19012            .as_ref()
19013            .expect("Should have the context menu deployed");
19014        match context_menu {
19015            CodeContextMenu::Completions(completions_menu) => {
19016                let completions = completions_menu.completions.borrow_mut();
19017                assert_eq!(
19018                    completions
19019                        .iter()
19020                        .map(|completion| &completion.label.text)
19021                        .collect::<Vec<_>>(),
19022                    vec!["method id()", "other"]
19023                )
19024            }
19025            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19026        }
19027    });
19028
19029    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
19030        let item1 = item1.clone();
19031        move |_, item_to_resolve, _| {
19032            let item1 = item1.clone();
19033            async move {
19034                if item1 == item_to_resolve {
19035                    Ok(lsp::CompletionItem {
19036                        label: "method id()".to_string(),
19037                        filter_text: Some("id".to_string()),
19038                        detail: Some("Now resolved!".to_string()),
19039                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
19040                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19041                            range: lsp::Range::new(
19042                                lsp::Position::new(0, 22),
19043                                lsp::Position::new(0, 22),
19044                            ),
19045                            new_text: ".id".to_string(),
19046                        })),
19047                        ..lsp::CompletionItem::default()
19048                    })
19049                } else {
19050                    Ok(item_to_resolve)
19051                }
19052            }
19053        }
19054    })
19055    .next()
19056    .await
19057    .unwrap();
19058    cx.run_until_parked();
19059
19060    cx.update_editor(|editor, window, cx| {
19061        editor.context_menu_next(&Default::default(), window, cx);
19062    });
19063    cx.run_until_parked();
19064
19065    cx.update_editor(|editor, _, _| {
19066        let context_menu = editor.context_menu.borrow_mut();
19067        let context_menu = context_menu
19068            .as_ref()
19069            .expect("Should have the context menu deployed");
19070        match context_menu {
19071            CodeContextMenu::Completions(completions_menu) => {
19072                let completions = completions_menu.completions.borrow_mut();
19073                assert_eq!(
19074                    completions
19075                        .iter()
19076                        .map(|completion| &completion.label.text)
19077                        .collect::<Vec<_>>(),
19078                    vec!["method id() Now resolved!", "other"],
19079                    "Should update first completion label, but not second as the filter text did not match."
19080                );
19081            }
19082            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19083        }
19084    });
19085}
19086
19087#[gpui::test]
19088async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
19089    init_test(cx, |_| {});
19090    let mut cx = EditorLspTestContext::new_rust(
19091        lsp::ServerCapabilities {
19092            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
19093            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
19094            completion_provider: Some(lsp::CompletionOptions {
19095                resolve_provider: Some(true),
19096                ..Default::default()
19097            }),
19098            ..Default::default()
19099        },
19100        cx,
19101    )
19102    .await;
19103    cx.set_state(indoc! {"
19104        struct TestStruct {
19105            field: i32
19106        }
19107
19108        fn mainˇ() {
19109            let unused_var = 42;
19110            let test_struct = TestStruct { field: 42 };
19111        }
19112    "});
19113    let symbol_range = cx.lsp_range(indoc! {"
19114        struct TestStruct {
19115            field: i32
19116        }
19117
19118        «fn main»() {
19119            let unused_var = 42;
19120            let test_struct = TestStruct { field: 42 };
19121        }
19122    "});
19123    let mut hover_requests =
19124        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
19125            Ok(Some(lsp::Hover {
19126                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
19127                    kind: lsp::MarkupKind::Markdown,
19128                    value: "Function documentation".to_string(),
19129                }),
19130                range: Some(symbol_range),
19131            }))
19132        });
19133
19134    // Case 1: Test that code action menu hide hover popover
19135    cx.dispatch_action(Hover);
19136    hover_requests.next().await;
19137    cx.condition(|editor, _| editor.hover_state.visible()).await;
19138    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
19139        move |_, _, _| async move {
19140            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
19141                lsp::CodeAction {
19142                    title: "Remove unused variable".to_string(),
19143                    kind: Some(CodeActionKind::QUICKFIX),
19144                    edit: Some(lsp::WorkspaceEdit {
19145                        changes: Some(
19146                            [(
19147                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
19148                                vec![lsp::TextEdit {
19149                                    range: lsp::Range::new(
19150                                        lsp::Position::new(5, 4),
19151                                        lsp::Position::new(5, 27),
19152                                    ),
19153                                    new_text: "".to_string(),
19154                                }],
19155                            )]
19156                            .into_iter()
19157                            .collect(),
19158                        ),
19159                        ..Default::default()
19160                    }),
19161                    ..Default::default()
19162                },
19163            )]))
19164        },
19165    );
19166    cx.update_editor(|editor, window, cx| {
19167        editor.toggle_code_actions(
19168            &ToggleCodeActions {
19169                deployed_from: None,
19170                quick_launch: false,
19171            },
19172            window,
19173            cx,
19174        );
19175    });
19176    code_action_requests.next().await;
19177    cx.run_until_parked();
19178    cx.condition(|editor, _| editor.context_menu_visible())
19179        .await;
19180    cx.update_editor(|editor, _, _| {
19181        assert!(
19182            !editor.hover_state.visible(),
19183            "Hover popover should be hidden when code action menu is shown"
19184        );
19185        // Hide code actions
19186        editor.context_menu.take();
19187    });
19188
19189    // Case 2: Test that code completions hide hover popover
19190    cx.dispatch_action(Hover);
19191    hover_requests.next().await;
19192    cx.condition(|editor, _| editor.hover_state.visible()).await;
19193    let counter = Arc::new(AtomicUsize::new(0));
19194    let mut completion_requests =
19195        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19196            let counter = counter.clone();
19197            async move {
19198                counter.fetch_add(1, atomic::Ordering::Release);
19199                Ok(Some(lsp::CompletionResponse::Array(vec![
19200                    lsp::CompletionItem {
19201                        label: "main".into(),
19202                        kind: Some(lsp::CompletionItemKind::FUNCTION),
19203                        detail: Some("() -> ()".to_string()),
19204                        ..Default::default()
19205                    },
19206                    lsp::CompletionItem {
19207                        label: "TestStruct".into(),
19208                        kind: Some(lsp::CompletionItemKind::STRUCT),
19209                        detail: Some("struct TestStruct".to_string()),
19210                        ..Default::default()
19211                    },
19212                ])))
19213            }
19214        });
19215    cx.update_editor(|editor, window, cx| {
19216        editor.show_completions(&ShowCompletions, window, cx);
19217    });
19218    completion_requests.next().await;
19219    cx.condition(|editor, _| editor.context_menu_visible())
19220        .await;
19221    cx.update_editor(|editor, _, _| {
19222        assert!(
19223            !editor.hover_state.visible(),
19224            "Hover popover should be hidden when completion menu is shown"
19225        );
19226    });
19227}
19228
19229#[gpui::test]
19230async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
19231    init_test(cx, |_| {});
19232
19233    let mut cx = EditorLspTestContext::new_rust(
19234        lsp::ServerCapabilities {
19235            completion_provider: Some(lsp::CompletionOptions {
19236                trigger_characters: Some(vec![".".to_string()]),
19237                resolve_provider: Some(true),
19238                ..Default::default()
19239            }),
19240            ..Default::default()
19241        },
19242        cx,
19243    )
19244    .await;
19245
19246    cx.set_state("fn main() { let a = 2ˇ; }");
19247    cx.simulate_keystroke(".");
19248
19249    let unresolved_item_1 = lsp::CompletionItem {
19250        label: "id".to_string(),
19251        filter_text: Some("id".to_string()),
19252        detail: None,
19253        documentation: None,
19254        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19255            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19256            new_text: ".id".to_string(),
19257        })),
19258        ..lsp::CompletionItem::default()
19259    };
19260    let resolved_item_1 = lsp::CompletionItem {
19261        additional_text_edits: Some(vec![lsp::TextEdit {
19262            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
19263            new_text: "!!".to_string(),
19264        }]),
19265        ..unresolved_item_1.clone()
19266    };
19267    let unresolved_item_2 = lsp::CompletionItem {
19268        label: "other".to_string(),
19269        filter_text: Some("other".to_string()),
19270        detail: None,
19271        documentation: None,
19272        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19273            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19274            new_text: ".other".to_string(),
19275        })),
19276        ..lsp::CompletionItem::default()
19277    };
19278    let resolved_item_2 = lsp::CompletionItem {
19279        additional_text_edits: Some(vec![lsp::TextEdit {
19280            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
19281            new_text: "??".to_string(),
19282        }]),
19283        ..unresolved_item_2.clone()
19284    };
19285
19286    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
19287    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
19288    cx.lsp
19289        .server
19290        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19291            let unresolved_item_1 = unresolved_item_1.clone();
19292            let resolved_item_1 = resolved_item_1.clone();
19293            let unresolved_item_2 = unresolved_item_2.clone();
19294            let resolved_item_2 = resolved_item_2.clone();
19295            let resolve_requests_1 = resolve_requests_1.clone();
19296            let resolve_requests_2 = resolve_requests_2.clone();
19297            move |unresolved_request, _| {
19298                let unresolved_item_1 = unresolved_item_1.clone();
19299                let resolved_item_1 = resolved_item_1.clone();
19300                let unresolved_item_2 = unresolved_item_2.clone();
19301                let resolved_item_2 = resolved_item_2.clone();
19302                let resolve_requests_1 = resolve_requests_1.clone();
19303                let resolve_requests_2 = resolve_requests_2.clone();
19304                async move {
19305                    if unresolved_request == unresolved_item_1 {
19306                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
19307                        Ok(resolved_item_1.clone())
19308                    } else if unresolved_request == unresolved_item_2 {
19309                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
19310                        Ok(resolved_item_2.clone())
19311                    } else {
19312                        panic!("Unexpected completion item {unresolved_request:?}")
19313                    }
19314                }
19315            }
19316        })
19317        .detach();
19318
19319    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19320        let unresolved_item_1 = unresolved_item_1.clone();
19321        let unresolved_item_2 = unresolved_item_2.clone();
19322        async move {
19323            Ok(Some(lsp::CompletionResponse::Array(vec![
19324                unresolved_item_1,
19325                unresolved_item_2,
19326            ])))
19327        }
19328    })
19329    .next()
19330    .await;
19331
19332    cx.condition(|editor, _| editor.context_menu_visible())
19333        .await;
19334    cx.update_editor(|editor, _, _| {
19335        let context_menu = editor.context_menu.borrow_mut();
19336        let context_menu = context_menu
19337            .as_ref()
19338            .expect("Should have the context menu deployed");
19339        match context_menu {
19340            CodeContextMenu::Completions(completions_menu) => {
19341                let completions = completions_menu.completions.borrow_mut();
19342                assert_eq!(
19343                    completions
19344                        .iter()
19345                        .map(|completion| &completion.label.text)
19346                        .collect::<Vec<_>>(),
19347                    vec!["id", "other"]
19348                )
19349            }
19350            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19351        }
19352    });
19353    cx.run_until_parked();
19354
19355    cx.update_editor(|editor, window, cx| {
19356        editor.context_menu_next(&ContextMenuNext, window, cx);
19357    });
19358    cx.run_until_parked();
19359    cx.update_editor(|editor, window, cx| {
19360        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19361    });
19362    cx.run_until_parked();
19363    cx.update_editor(|editor, window, cx| {
19364        editor.context_menu_next(&ContextMenuNext, window, cx);
19365    });
19366    cx.run_until_parked();
19367    cx.update_editor(|editor, window, cx| {
19368        editor
19369            .compose_completion(&ComposeCompletion::default(), window, cx)
19370            .expect("No task returned")
19371    })
19372    .await
19373    .expect("Completion failed");
19374    cx.run_until_parked();
19375
19376    cx.update_editor(|editor, _, cx| {
19377        assert_eq!(
19378            resolve_requests_1.load(atomic::Ordering::Acquire),
19379            1,
19380            "Should always resolve once despite multiple selections"
19381        );
19382        assert_eq!(
19383            resolve_requests_2.load(atomic::Ordering::Acquire),
19384            1,
19385            "Should always resolve once after multiple selections and applying the completion"
19386        );
19387        assert_eq!(
19388            editor.text(cx),
19389            "fn main() { let a = ??.other; }",
19390            "Should use resolved data when applying the completion"
19391        );
19392    });
19393}
19394
19395#[gpui::test]
19396async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
19397    init_test(cx, |_| {});
19398
19399    let item_0 = lsp::CompletionItem {
19400        label: "abs".into(),
19401        insert_text: Some("abs".into()),
19402        data: Some(json!({ "very": "special"})),
19403        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
19404        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19405            lsp::InsertReplaceEdit {
19406                new_text: "abs".to_string(),
19407                insert: lsp::Range::default(),
19408                replace: lsp::Range::default(),
19409            },
19410        )),
19411        ..lsp::CompletionItem::default()
19412    };
19413    let items = iter::once(item_0.clone())
19414        .chain((11..51).map(|i| lsp::CompletionItem {
19415            label: format!("item_{}", i),
19416            insert_text: Some(format!("item_{}", i)),
19417            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
19418            ..lsp::CompletionItem::default()
19419        }))
19420        .collect::<Vec<_>>();
19421
19422    let default_commit_characters = vec!["?".to_string()];
19423    let default_data = json!({ "default": "data"});
19424    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
19425    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
19426    let default_edit_range = lsp::Range {
19427        start: lsp::Position {
19428            line: 0,
19429            character: 5,
19430        },
19431        end: lsp::Position {
19432            line: 0,
19433            character: 5,
19434        },
19435    };
19436
19437    let mut cx = EditorLspTestContext::new_rust(
19438        lsp::ServerCapabilities {
19439            completion_provider: Some(lsp::CompletionOptions {
19440                trigger_characters: Some(vec![".".to_string()]),
19441                resolve_provider: Some(true),
19442                ..Default::default()
19443            }),
19444            ..Default::default()
19445        },
19446        cx,
19447    )
19448    .await;
19449
19450    cx.set_state("fn main() { let a = 2ˇ; }");
19451    cx.simulate_keystroke(".");
19452
19453    let completion_data = default_data.clone();
19454    let completion_characters = default_commit_characters.clone();
19455    let completion_items = items.clone();
19456    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19457        let default_data = completion_data.clone();
19458        let default_commit_characters = completion_characters.clone();
19459        let items = completion_items.clone();
19460        async move {
19461            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
19462                items,
19463                item_defaults: Some(lsp::CompletionListItemDefaults {
19464                    data: Some(default_data.clone()),
19465                    commit_characters: Some(default_commit_characters.clone()),
19466                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
19467                        default_edit_range,
19468                    )),
19469                    insert_text_format: Some(default_insert_text_format),
19470                    insert_text_mode: Some(default_insert_text_mode),
19471                }),
19472                ..lsp::CompletionList::default()
19473            })))
19474        }
19475    })
19476    .next()
19477    .await;
19478
19479    let resolved_items = Arc::new(Mutex::new(Vec::new()));
19480    cx.lsp
19481        .server
19482        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19483            let closure_resolved_items = resolved_items.clone();
19484            move |item_to_resolve, _| {
19485                let closure_resolved_items = closure_resolved_items.clone();
19486                async move {
19487                    closure_resolved_items.lock().push(item_to_resolve.clone());
19488                    Ok(item_to_resolve)
19489                }
19490            }
19491        })
19492        .detach();
19493
19494    cx.condition(|editor, _| editor.context_menu_visible())
19495        .await;
19496    cx.run_until_parked();
19497    cx.update_editor(|editor, _, _| {
19498        let menu = editor.context_menu.borrow_mut();
19499        match menu.as_ref().expect("should have the completions menu") {
19500            CodeContextMenu::Completions(completions_menu) => {
19501                assert_eq!(
19502                    completions_menu
19503                        .entries
19504                        .borrow()
19505                        .iter()
19506                        .map(|mat| mat.string.clone())
19507                        .collect::<Vec<String>>(),
19508                    items
19509                        .iter()
19510                        .map(|completion| completion.label.clone())
19511                        .collect::<Vec<String>>()
19512                );
19513            }
19514            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
19515        }
19516    });
19517    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
19518    // with 4 from the end.
19519    assert_eq!(
19520        *resolved_items.lock(),
19521        [&items[0..16], &items[items.len() - 4..items.len()]]
19522            .concat()
19523            .iter()
19524            .cloned()
19525            .map(|mut item| {
19526                if item.data.is_none() {
19527                    item.data = Some(default_data.clone());
19528                }
19529                item
19530            })
19531            .collect::<Vec<lsp::CompletionItem>>(),
19532        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
19533    );
19534    resolved_items.lock().clear();
19535
19536    cx.update_editor(|editor, window, cx| {
19537        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19538    });
19539    cx.run_until_parked();
19540    // Completions that have already been resolved are skipped.
19541    assert_eq!(
19542        *resolved_items.lock(),
19543        items[items.len() - 17..items.len() - 4]
19544            .iter()
19545            .cloned()
19546            .map(|mut item| {
19547                if item.data.is_none() {
19548                    item.data = Some(default_data.clone());
19549                }
19550                item
19551            })
19552            .collect::<Vec<lsp::CompletionItem>>()
19553    );
19554    resolved_items.lock().clear();
19555}
19556
19557#[gpui::test]
19558async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
19559    init_test(cx, |_| {});
19560
19561    let mut cx = EditorLspTestContext::new(
19562        Language::new(
19563            LanguageConfig {
19564                matcher: LanguageMatcher {
19565                    path_suffixes: vec!["jsx".into()],
19566                    ..Default::default()
19567                },
19568                overrides: [(
19569                    "element".into(),
19570                    LanguageConfigOverride {
19571                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
19572                        ..Default::default()
19573                    },
19574                )]
19575                .into_iter()
19576                .collect(),
19577                ..Default::default()
19578            },
19579            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
19580        )
19581        .with_override_query("(jsx_self_closing_element) @element")
19582        .unwrap(),
19583        lsp::ServerCapabilities {
19584            completion_provider: Some(lsp::CompletionOptions {
19585                trigger_characters: Some(vec![":".to_string()]),
19586                ..Default::default()
19587            }),
19588            ..Default::default()
19589        },
19590        cx,
19591    )
19592    .await;
19593
19594    cx.lsp
19595        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19596            Ok(Some(lsp::CompletionResponse::Array(vec![
19597                lsp::CompletionItem {
19598                    label: "bg-blue".into(),
19599                    ..Default::default()
19600                },
19601                lsp::CompletionItem {
19602                    label: "bg-red".into(),
19603                    ..Default::default()
19604                },
19605                lsp::CompletionItem {
19606                    label: "bg-yellow".into(),
19607                    ..Default::default()
19608                },
19609            ])))
19610        });
19611
19612    cx.set_state(r#"<p class="bgˇ" />"#);
19613
19614    // Trigger completion when typing a dash, because the dash is an extra
19615    // word character in the 'element' scope, which contains the cursor.
19616    cx.simulate_keystroke("-");
19617    cx.executor().run_until_parked();
19618    cx.update_editor(|editor, _, _| {
19619        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19620        {
19621            assert_eq!(
19622                completion_menu_entries(menu),
19623                &["bg-blue", "bg-red", "bg-yellow"]
19624            );
19625        } else {
19626            panic!("expected completion menu to be open");
19627        }
19628    });
19629
19630    cx.simulate_keystroke("l");
19631    cx.executor().run_until_parked();
19632    cx.update_editor(|editor, _, _| {
19633        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19634        {
19635            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
19636        } else {
19637            panic!("expected completion menu to be open");
19638        }
19639    });
19640
19641    // When filtering completions, consider the character after the '-' to
19642    // be the start of a subword.
19643    cx.set_state(r#"<p class="yelˇ" />"#);
19644    cx.simulate_keystroke("l");
19645    cx.executor().run_until_parked();
19646    cx.update_editor(|editor, _, _| {
19647        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19648        {
19649            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
19650        } else {
19651            panic!("expected completion menu to be open");
19652        }
19653    });
19654}
19655
19656fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19657    let entries = menu.entries.borrow();
19658    entries.iter().map(|mat| mat.string.clone()).collect()
19659}
19660
19661#[gpui::test]
19662async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19663    init_test(cx, |settings| {
19664        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19665    });
19666
19667    let fs = FakeFs::new(cx.executor());
19668    fs.insert_file(path!("/file.ts"), Default::default()).await;
19669
19670    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19671    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19672
19673    language_registry.add(Arc::new(Language::new(
19674        LanguageConfig {
19675            name: "TypeScript".into(),
19676            matcher: LanguageMatcher {
19677                path_suffixes: vec!["ts".to_string()],
19678                ..Default::default()
19679            },
19680            ..Default::default()
19681        },
19682        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19683    )));
19684    update_test_language_settings(cx, |settings| {
19685        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19686    });
19687
19688    let test_plugin = "test_plugin";
19689    let _ = language_registry.register_fake_lsp(
19690        "TypeScript",
19691        FakeLspAdapter {
19692            prettier_plugins: vec![test_plugin],
19693            ..Default::default()
19694        },
19695    );
19696
19697    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19698    let buffer = project
19699        .update(cx, |project, cx| {
19700            project.open_local_buffer(path!("/file.ts"), cx)
19701        })
19702        .await
19703        .unwrap();
19704
19705    let buffer_text = "one\ntwo\nthree\n";
19706    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19707    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19708    editor.update_in(cx, |editor, window, cx| {
19709        editor.set_text(buffer_text, window, cx)
19710    });
19711
19712    editor
19713        .update_in(cx, |editor, window, cx| {
19714            editor.perform_format(
19715                project.clone(),
19716                FormatTrigger::Manual,
19717                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19718                window,
19719                cx,
19720            )
19721        })
19722        .unwrap()
19723        .await;
19724    assert_eq!(
19725        editor.update(cx, |editor, cx| editor.text(cx)),
19726        buffer_text.to_string() + prettier_format_suffix,
19727        "Test prettier formatting was not applied to the original buffer text",
19728    );
19729
19730    update_test_language_settings(cx, |settings| {
19731        settings.defaults.formatter = Some(FormatterList::default())
19732    });
19733    let format = editor.update_in(cx, |editor, window, cx| {
19734        editor.perform_format(
19735            project.clone(),
19736            FormatTrigger::Manual,
19737            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19738            window,
19739            cx,
19740        )
19741    });
19742    format.await.unwrap();
19743    assert_eq!(
19744        editor.update(cx, |editor, cx| editor.text(cx)),
19745        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19746        "Autoformatting (via test prettier) was not applied to the original buffer text",
19747    );
19748}
19749
19750#[gpui::test]
19751async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
19752    init_test(cx, |settings| {
19753        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19754    });
19755
19756    let fs = FakeFs::new(cx.executor());
19757    fs.insert_file(path!("/file.settings"), Default::default())
19758        .await;
19759
19760    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
19761    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19762
19763    let ts_lang = Arc::new(Language::new(
19764        LanguageConfig {
19765            name: "TypeScript".into(),
19766            matcher: LanguageMatcher {
19767                path_suffixes: vec!["ts".to_string()],
19768                ..LanguageMatcher::default()
19769            },
19770            prettier_parser_name: Some("typescript".to_string()),
19771            ..LanguageConfig::default()
19772        },
19773        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19774    ));
19775
19776    language_registry.add(ts_lang.clone());
19777
19778    update_test_language_settings(cx, |settings| {
19779        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19780    });
19781
19782    let test_plugin = "test_plugin";
19783    let _ = language_registry.register_fake_lsp(
19784        "TypeScript",
19785        FakeLspAdapter {
19786            prettier_plugins: vec![test_plugin],
19787            ..Default::default()
19788        },
19789    );
19790
19791    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19792    let buffer = project
19793        .update(cx, |project, cx| {
19794            project.open_local_buffer(path!("/file.settings"), cx)
19795        })
19796        .await
19797        .unwrap();
19798
19799    project.update(cx, |project, cx| {
19800        project.set_language_for_buffer(&buffer, ts_lang, cx)
19801    });
19802
19803    let buffer_text = "one\ntwo\nthree\n";
19804    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19805    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19806    editor.update_in(cx, |editor, window, cx| {
19807        editor.set_text(buffer_text, window, cx)
19808    });
19809
19810    editor
19811        .update_in(cx, |editor, window, cx| {
19812            editor.perform_format(
19813                project.clone(),
19814                FormatTrigger::Manual,
19815                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19816                window,
19817                cx,
19818            )
19819        })
19820        .unwrap()
19821        .await;
19822    assert_eq!(
19823        editor.update(cx, |editor, cx| editor.text(cx)),
19824        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
19825        "Test prettier formatting was not applied to the original buffer text",
19826    );
19827
19828    update_test_language_settings(cx, |settings| {
19829        settings.defaults.formatter = Some(FormatterList::default())
19830    });
19831    let format = editor.update_in(cx, |editor, window, cx| {
19832        editor.perform_format(
19833            project.clone(),
19834            FormatTrigger::Manual,
19835            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19836            window,
19837            cx,
19838        )
19839    });
19840    format.await.unwrap();
19841
19842    assert_eq!(
19843        editor.update(cx, |editor, cx| editor.text(cx)),
19844        buffer_text.to_string()
19845            + prettier_format_suffix
19846            + "\ntypescript\n"
19847            + prettier_format_suffix
19848            + "\ntypescript",
19849        "Autoformatting (via test prettier) was not applied to the original buffer text",
19850    );
19851}
19852
19853#[gpui::test]
19854async fn test_addition_reverts(cx: &mut TestAppContext) {
19855    init_test(cx, |_| {});
19856    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19857    let base_text = indoc! {r#"
19858        struct Row;
19859        struct Row1;
19860        struct Row2;
19861
19862        struct Row4;
19863        struct Row5;
19864        struct Row6;
19865
19866        struct Row8;
19867        struct Row9;
19868        struct Row10;"#};
19869
19870    // When addition hunks are not adjacent to carets, no hunk revert is performed
19871    assert_hunk_revert(
19872        indoc! {r#"struct Row;
19873                   struct Row1;
19874                   struct Row1.1;
19875                   struct Row1.2;
19876                   struct Row2;ˇ
19877
19878                   struct Row4;
19879                   struct Row5;
19880                   struct Row6;
19881
19882                   struct Row8;
19883                   ˇstruct Row9;
19884                   struct Row9.1;
19885                   struct Row9.2;
19886                   struct Row9.3;
19887                   struct Row10;"#},
19888        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19889        indoc! {r#"struct Row;
19890                   struct Row1;
19891                   struct Row1.1;
19892                   struct Row1.2;
19893                   struct Row2;ˇ
19894
19895                   struct Row4;
19896                   struct Row5;
19897                   struct Row6;
19898
19899                   struct Row8;
19900                   ˇstruct Row9;
19901                   struct Row9.1;
19902                   struct Row9.2;
19903                   struct Row9.3;
19904                   struct Row10;"#},
19905        base_text,
19906        &mut cx,
19907    );
19908    // Same for selections
19909    assert_hunk_revert(
19910        indoc! {r#"struct Row;
19911                   struct Row1;
19912                   struct Row2;
19913                   struct Row2.1;
19914                   struct Row2.2;
19915                   «ˇ
19916                   struct Row4;
19917                   struct» Row5;
19918                   «struct Row6;
19919                   ˇ»
19920                   struct Row9.1;
19921                   struct Row9.2;
19922                   struct Row9.3;
19923                   struct Row8;
19924                   struct Row9;
19925                   struct Row10;"#},
19926        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19927        indoc! {r#"struct Row;
19928                   struct Row1;
19929                   struct Row2;
19930                   struct Row2.1;
19931                   struct Row2.2;
19932                   «ˇ
19933                   struct Row4;
19934                   struct» Row5;
19935                   «struct Row6;
19936                   ˇ»
19937                   struct Row9.1;
19938                   struct Row9.2;
19939                   struct Row9.3;
19940                   struct Row8;
19941                   struct Row9;
19942                   struct Row10;"#},
19943        base_text,
19944        &mut cx,
19945    );
19946
19947    // When carets and selections intersect the addition hunks, those are reverted.
19948    // Adjacent carets got merged.
19949    assert_hunk_revert(
19950        indoc! {r#"struct Row;
19951                   ˇ// something on the top
19952                   struct Row1;
19953                   struct Row2;
19954                   struct Roˇw3.1;
19955                   struct Row2.2;
19956                   struct Row2.3;ˇ
19957
19958                   struct Row4;
19959                   struct ˇRow5.1;
19960                   struct Row5.2;
19961                   struct «Rowˇ»5.3;
19962                   struct Row5;
19963                   struct Row6;
19964                   ˇ
19965                   struct Row9.1;
19966                   struct «Rowˇ»9.2;
19967                   struct «ˇRow»9.3;
19968                   struct Row8;
19969                   struct Row9;
19970                   «ˇ// something on bottom»
19971                   struct Row10;"#},
19972        vec![
19973            DiffHunkStatusKind::Added,
19974            DiffHunkStatusKind::Added,
19975            DiffHunkStatusKind::Added,
19976            DiffHunkStatusKind::Added,
19977            DiffHunkStatusKind::Added,
19978        ],
19979        indoc! {r#"struct Row;
19980                   ˇstruct Row1;
19981                   struct Row2;
19982                   ˇ
19983                   struct Row4;
19984                   ˇstruct Row5;
19985                   struct Row6;
19986                   ˇ
19987                   ˇstruct Row8;
19988                   struct Row9;
19989                   ˇstruct Row10;"#},
19990        base_text,
19991        &mut cx,
19992    );
19993}
19994
19995#[gpui::test]
19996async fn test_modification_reverts(cx: &mut TestAppContext) {
19997    init_test(cx, |_| {});
19998    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19999    let base_text = indoc! {r#"
20000        struct Row;
20001        struct Row1;
20002        struct Row2;
20003
20004        struct Row4;
20005        struct Row5;
20006        struct Row6;
20007
20008        struct Row8;
20009        struct Row9;
20010        struct Row10;"#};
20011
20012    // Modification hunks behave the same as the addition ones.
20013    assert_hunk_revert(
20014        indoc! {r#"struct Row;
20015                   struct Row1;
20016                   struct Row33;
20017                   ˇ
20018                   struct Row4;
20019                   struct Row5;
20020                   struct Row6;
20021                   ˇ
20022                   struct Row99;
20023                   struct Row9;
20024                   struct Row10;"#},
20025        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
20026        indoc! {r#"struct Row;
20027                   struct Row1;
20028                   struct Row33;
20029                   ˇ
20030                   struct Row4;
20031                   struct Row5;
20032                   struct Row6;
20033                   ˇ
20034                   struct Row99;
20035                   struct Row9;
20036                   struct Row10;"#},
20037        base_text,
20038        &mut cx,
20039    );
20040    assert_hunk_revert(
20041        indoc! {r#"struct Row;
20042                   struct Row1;
20043                   struct Row33;
20044                   «ˇ
20045                   struct Row4;
20046                   struct» Row5;
20047                   «struct Row6;
20048                   ˇ»
20049                   struct Row99;
20050                   struct Row9;
20051                   struct Row10;"#},
20052        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
20053        indoc! {r#"struct Row;
20054                   struct Row1;
20055                   struct Row33;
20056                   «ˇ
20057                   struct Row4;
20058                   struct» Row5;
20059                   «struct Row6;
20060                   ˇ»
20061                   struct Row99;
20062                   struct Row9;
20063                   struct Row10;"#},
20064        base_text,
20065        &mut cx,
20066    );
20067
20068    assert_hunk_revert(
20069        indoc! {r#"ˇstruct Row1.1;
20070                   struct Row1;
20071                   «ˇstr»uct Row22;
20072
20073                   struct ˇRow44;
20074                   struct Row5;
20075                   struct «Rˇ»ow66;ˇ
20076
20077                   «struˇ»ct Row88;
20078                   struct Row9;
20079                   struct Row1011;ˇ"#},
20080        vec![
20081            DiffHunkStatusKind::Modified,
20082            DiffHunkStatusKind::Modified,
20083            DiffHunkStatusKind::Modified,
20084            DiffHunkStatusKind::Modified,
20085            DiffHunkStatusKind::Modified,
20086            DiffHunkStatusKind::Modified,
20087        ],
20088        indoc! {r#"struct Row;
20089                   ˇstruct Row1;
20090                   struct Row2;
20091                   ˇ
20092                   struct Row4;
20093                   ˇstruct Row5;
20094                   struct Row6;
20095                   ˇ
20096                   struct Row8;
20097                   ˇstruct Row9;
20098                   struct Row10;ˇ"#},
20099        base_text,
20100        &mut cx,
20101    );
20102}
20103
20104#[gpui::test]
20105async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
20106    init_test(cx, |_| {});
20107    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
20108    let base_text = indoc! {r#"
20109        one
20110
20111        two
20112        three
20113        "#};
20114
20115    cx.set_head_text(base_text);
20116    cx.set_state("\nˇ\n");
20117    cx.executor().run_until_parked();
20118    cx.update_editor(|editor, _window, cx| {
20119        editor.expand_selected_diff_hunks(cx);
20120    });
20121    cx.executor().run_until_parked();
20122    cx.update_editor(|editor, window, cx| {
20123        editor.backspace(&Default::default(), window, cx);
20124    });
20125    cx.run_until_parked();
20126    cx.assert_state_with_diff(
20127        indoc! {r#"
20128
20129        - two
20130        - threeˇ
20131        +
20132        "#}
20133        .to_string(),
20134    );
20135}
20136
20137#[gpui::test]
20138async fn test_deletion_reverts(cx: &mut TestAppContext) {
20139    init_test(cx, |_| {});
20140    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
20141    let base_text = indoc! {r#"struct Row;
20142struct Row1;
20143struct Row2;
20144
20145struct Row4;
20146struct Row5;
20147struct Row6;
20148
20149struct Row8;
20150struct Row9;
20151struct Row10;"#};
20152
20153    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
20154    assert_hunk_revert(
20155        indoc! {r#"struct Row;
20156                   struct Row2;
20157
20158                   ˇstruct Row4;
20159                   struct Row5;
20160                   struct Row6;
20161                   ˇ
20162                   struct Row8;
20163                   struct Row10;"#},
20164        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20165        indoc! {r#"struct Row;
20166                   struct Row2;
20167
20168                   ˇstruct Row4;
20169                   struct Row5;
20170                   struct Row6;
20171                   ˇ
20172                   struct Row8;
20173                   struct Row10;"#},
20174        base_text,
20175        &mut cx,
20176    );
20177    assert_hunk_revert(
20178        indoc! {r#"struct Row;
20179                   struct Row2;
20180
20181                   «ˇstruct Row4;
20182                   struct» Row5;
20183                   «struct Row6;
20184                   ˇ»
20185                   struct Row8;
20186                   struct Row10;"#},
20187        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20188        indoc! {r#"struct Row;
20189                   struct Row2;
20190
20191                   «ˇstruct Row4;
20192                   struct» Row5;
20193                   «struct Row6;
20194                   ˇ»
20195                   struct Row8;
20196                   struct Row10;"#},
20197        base_text,
20198        &mut cx,
20199    );
20200
20201    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
20202    assert_hunk_revert(
20203        indoc! {r#"struct Row;
20204                   ˇstruct Row2;
20205
20206                   struct Row4;
20207                   struct Row5;
20208                   struct Row6;
20209
20210                   struct Row8;ˇ
20211                   struct Row10;"#},
20212        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20213        indoc! {r#"struct Row;
20214                   struct Row1;
20215                   ˇstruct Row2;
20216
20217                   struct Row4;
20218                   struct Row5;
20219                   struct Row6;
20220
20221                   struct Row8;ˇ
20222                   struct Row9;
20223                   struct Row10;"#},
20224        base_text,
20225        &mut cx,
20226    );
20227    assert_hunk_revert(
20228        indoc! {r#"struct Row;
20229                   struct Row2«ˇ;
20230                   struct Row4;
20231                   struct» Row5;
20232                   «struct Row6;
20233
20234                   struct Row8;ˇ»
20235                   struct Row10;"#},
20236        vec![
20237            DiffHunkStatusKind::Deleted,
20238            DiffHunkStatusKind::Deleted,
20239            DiffHunkStatusKind::Deleted,
20240        ],
20241        indoc! {r#"struct Row;
20242                   struct Row1;
20243                   struct Row2«ˇ;
20244
20245                   struct Row4;
20246                   struct» Row5;
20247                   «struct Row6;
20248
20249                   struct Row8;ˇ»
20250                   struct Row9;
20251                   struct Row10;"#},
20252        base_text,
20253        &mut cx,
20254    );
20255}
20256
20257#[gpui::test]
20258async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
20259    init_test(cx, |_| {});
20260
20261    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
20262    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
20263    let base_text_3 =
20264        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
20265
20266    let text_1 = edit_first_char_of_every_line(base_text_1);
20267    let text_2 = edit_first_char_of_every_line(base_text_2);
20268    let text_3 = edit_first_char_of_every_line(base_text_3);
20269
20270    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
20271    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
20272    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
20273
20274    let multibuffer = cx.new(|cx| {
20275        let mut multibuffer = MultiBuffer::new(ReadWrite);
20276        multibuffer.push_excerpts(
20277            buffer_1.clone(),
20278            [
20279                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20280                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20281                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20282            ],
20283            cx,
20284        );
20285        multibuffer.push_excerpts(
20286            buffer_2.clone(),
20287            [
20288                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20289                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20290                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20291            ],
20292            cx,
20293        );
20294        multibuffer.push_excerpts(
20295            buffer_3.clone(),
20296            [
20297                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20298                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20299                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20300            ],
20301            cx,
20302        );
20303        multibuffer
20304    });
20305
20306    let fs = FakeFs::new(cx.executor());
20307    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20308    let (editor, cx) = cx
20309        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
20310    editor.update_in(cx, |editor, _window, cx| {
20311        for (buffer, diff_base) in [
20312            (buffer_1.clone(), base_text_1),
20313            (buffer_2.clone(), base_text_2),
20314            (buffer_3.clone(), base_text_3),
20315        ] {
20316            let diff = cx.new(|cx| {
20317                BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20318            });
20319            editor
20320                .buffer
20321                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20322        }
20323    });
20324    cx.executor().run_until_parked();
20325
20326    editor.update_in(cx, |editor, window, cx| {
20327        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}");
20328        editor.select_all(&SelectAll, window, cx);
20329        editor.git_restore(&Default::default(), window, cx);
20330    });
20331    cx.executor().run_until_parked();
20332
20333    // When all ranges are selected, all buffer hunks are reverted.
20334    editor.update(cx, |editor, cx| {
20335        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");
20336    });
20337    buffer_1.update(cx, |buffer, _| {
20338        assert_eq!(buffer.text(), base_text_1);
20339    });
20340    buffer_2.update(cx, |buffer, _| {
20341        assert_eq!(buffer.text(), base_text_2);
20342    });
20343    buffer_3.update(cx, |buffer, _| {
20344        assert_eq!(buffer.text(), base_text_3);
20345    });
20346
20347    editor.update_in(cx, |editor, window, cx| {
20348        editor.undo(&Default::default(), window, cx);
20349    });
20350
20351    editor.update_in(cx, |editor, window, cx| {
20352        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20353            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
20354        });
20355        editor.git_restore(&Default::default(), window, cx);
20356    });
20357
20358    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
20359    // but not affect buffer_2 and its related excerpts.
20360    editor.update(cx, |editor, cx| {
20361        assert_eq!(
20362            editor.text(cx),
20363            "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}"
20364        );
20365    });
20366    buffer_1.update(cx, |buffer, _| {
20367        assert_eq!(buffer.text(), base_text_1);
20368    });
20369    buffer_2.update(cx, |buffer, _| {
20370        assert_eq!(
20371            buffer.text(),
20372            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
20373        );
20374    });
20375    buffer_3.update(cx, |buffer, _| {
20376        assert_eq!(
20377            buffer.text(),
20378            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
20379        );
20380    });
20381
20382    fn edit_first_char_of_every_line(text: &str) -> String {
20383        text.split('\n')
20384            .map(|line| format!("X{}", &line[1..]))
20385            .collect::<Vec<_>>()
20386            .join("\n")
20387    }
20388}
20389
20390#[gpui::test]
20391async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
20392    init_test(cx, |_| {});
20393
20394    let cols = 4;
20395    let rows = 10;
20396    let sample_text_1 = sample_text(rows, cols, 'a');
20397    assert_eq!(
20398        sample_text_1,
20399        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
20400    );
20401    let sample_text_2 = sample_text(rows, cols, 'l');
20402    assert_eq!(
20403        sample_text_2,
20404        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
20405    );
20406    let sample_text_3 = sample_text(rows, cols, 'v');
20407    assert_eq!(
20408        sample_text_3,
20409        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
20410    );
20411
20412    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
20413    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
20414    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
20415
20416    let multi_buffer = cx.new(|cx| {
20417        let mut multibuffer = MultiBuffer::new(ReadWrite);
20418        multibuffer.push_excerpts(
20419            buffer_1.clone(),
20420            [
20421                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20422                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20423                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20424            ],
20425            cx,
20426        );
20427        multibuffer.push_excerpts(
20428            buffer_2.clone(),
20429            [
20430                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20431                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20432                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20433            ],
20434            cx,
20435        );
20436        multibuffer.push_excerpts(
20437            buffer_3.clone(),
20438            [
20439                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20440                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20441                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20442            ],
20443            cx,
20444        );
20445        multibuffer
20446    });
20447
20448    let fs = FakeFs::new(cx.executor());
20449    fs.insert_tree(
20450        "/a",
20451        json!({
20452            "main.rs": sample_text_1,
20453            "other.rs": sample_text_2,
20454            "lib.rs": sample_text_3,
20455        }),
20456    )
20457    .await;
20458    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20459    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20460    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20461    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20462        Editor::new(
20463            EditorMode::full(),
20464            multi_buffer,
20465            Some(project.clone()),
20466            window,
20467            cx,
20468        )
20469    });
20470    let multibuffer_item_id = workspace
20471        .update(cx, |workspace, window, cx| {
20472            assert!(
20473                workspace.active_item(cx).is_none(),
20474                "active item should be None before the first item is added"
20475            );
20476            workspace.add_item_to_active_pane(
20477                Box::new(multi_buffer_editor.clone()),
20478                None,
20479                true,
20480                window,
20481                cx,
20482            );
20483            let active_item = workspace
20484                .active_item(cx)
20485                .expect("should have an active item after adding the multi buffer");
20486            assert_eq!(
20487                active_item.buffer_kind(cx),
20488                ItemBufferKind::Multibuffer,
20489                "A multi buffer was expected to active after adding"
20490            );
20491            active_item.item_id()
20492        })
20493        .unwrap();
20494    cx.executor().run_until_parked();
20495
20496    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20497        editor.change_selections(
20498            SelectionEffects::scroll(Autoscroll::Next),
20499            window,
20500            cx,
20501            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
20502        );
20503        editor.open_excerpts(&OpenExcerpts, window, cx);
20504    });
20505    cx.executor().run_until_parked();
20506    let first_item_id = workspace
20507        .update(cx, |workspace, window, cx| {
20508            let active_item = workspace
20509                .active_item(cx)
20510                .expect("should have an active item after navigating into the 1st buffer");
20511            let first_item_id = active_item.item_id();
20512            assert_ne!(
20513                first_item_id, multibuffer_item_id,
20514                "Should navigate into the 1st buffer and activate it"
20515            );
20516            assert_eq!(
20517                active_item.buffer_kind(cx),
20518                ItemBufferKind::Singleton,
20519                "New active item should be a singleton buffer"
20520            );
20521            assert_eq!(
20522                active_item
20523                    .act_as::<Editor>(cx)
20524                    .expect("should have navigated into an editor for the 1st buffer")
20525                    .read(cx)
20526                    .text(cx),
20527                sample_text_1
20528            );
20529
20530            workspace
20531                .go_back(workspace.active_pane().downgrade(), window, cx)
20532                .detach_and_log_err(cx);
20533
20534            first_item_id
20535        })
20536        .unwrap();
20537    cx.executor().run_until_parked();
20538    workspace
20539        .update(cx, |workspace, _, cx| {
20540            let active_item = workspace
20541                .active_item(cx)
20542                .expect("should have an active item after navigating back");
20543            assert_eq!(
20544                active_item.item_id(),
20545                multibuffer_item_id,
20546                "Should navigate back to the multi buffer"
20547            );
20548            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20549        })
20550        .unwrap();
20551
20552    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20553        editor.change_selections(
20554            SelectionEffects::scroll(Autoscroll::Next),
20555            window,
20556            cx,
20557            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
20558        );
20559        editor.open_excerpts(&OpenExcerpts, window, cx);
20560    });
20561    cx.executor().run_until_parked();
20562    let second_item_id = workspace
20563        .update(cx, |workspace, window, cx| {
20564            let active_item = workspace
20565                .active_item(cx)
20566                .expect("should have an active item after navigating into the 2nd buffer");
20567            let second_item_id = active_item.item_id();
20568            assert_ne!(
20569                second_item_id, multibuffer_item_id,
20570                "Should navigate away from the multibuffer"
20571            );
20572            assert_ne!(
20573                second_item_id, first_item_id,
20574                "Should navigate into the 2nd buffer and activate it"
20575            );
20576            assert_eq!(
20577                active_item.buffer_kind(cx),
20578                ItemBufferKind::Singleton,
20579                "New active item should be a singleton buffer"
20580            );
20581            assert_eq!(
20582                active_item
20583                    .act_as::<Editor>(cx)
20584                    .expect("should have navigated into an editor")
20585                    .read(cx)
20586                    .text(cx),
20587                sample_text_2
20588            );
20589
20590            workspace
20591                .go_back(workspace.active_pane().downgrade(), window, cx)
20592                .detach_and_log_err(cx);
20593
20594            second_item_id
20595        })
20596        .unwrap();
20597    cx.executor().run_until_parked();
20598    workspace
20599        .update(cx, |workspace, _, cx| {
20600            let active_item = workspace
20601                .active_item(cx)
20602                .expect("should have an active item after navigating back from the 2nd buffer");
20603            assert_eq!(
20604                active_item.item_id(),
20605                multibuffer_item_id,
20606                "Should navigate back from the 2nd buffer to the multi buffer"
20607            );
20608            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20609        })
20610        .unwrap();
20611
20612    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20613        editor.change_selections(
20614            SelectionEffects::scroll(Autoscroll::Next),
20615            window,
20616            cx,
20617            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
20618        );
20619        editor.open_excerpts(&OpenExcerpts, window, cx);
20620    });
20621    cx.executor().run_until_parked();
20622    workspace
20623        .update(cx, |workspace, window, cx| {
20624            let active_item = workspace
20625                .active_item(cx)
20626                .expect("should have an active item after navigating into the 3rd buffer");
20627            let third_item_id = active_item.item_id();
20628            assert_ne!(
20629                third_item_id, multibuffer_item_id,
20630                "Should navigate into the 3rd buffer and activate it"
20631            );
20632            assert_ne!(third_item_id, first_item_id);
20633            assert_ne!(third_item_id, second_item_id);
20634            assert_eq!(
20635                active_item.buffer_kind(cx),
20636                ItemBufferKind::Singleton,
20637                "New active item should be a singleton buffer"
20638            );
20639            assert_eq!(
20640                active_item
20641                    .act_as::<Editor>(cx)
20642                    .expect("should have navigated into an editor")
20643                    .read(cx)
20644                    .text(cx),
20645                sample_text_3
20646            );
20647
20648            workspace
20649                .go_back(workspace.active_pane().downgrade(), window, cx)
20650                .detach_and_log_err(cx);
20651        })
20652        .unwrap();
20653    cx.executor().run_until_parked();
20654    workspace
20655        .update(cx, |workspace, _, cx| {
20656            let active_item = workspace
20657                .active_item(cx)
20658                .expect("should have an active item after navigating back from the 3rd buffer");
20659            assert_eq!(
20660                active_item.item_id(),
20661                multibuffer_item_id,
20662                "Should navigate back from the 3rd buffer to the multi buffer"
20663            );
20664            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20665        })
20666        .unwrap();
20667}
20668
20669#[gpui::test]
20670async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20671    init_test(cx, |_| {});
20672
20673    let mut cx = EditorTestContext::new(cx).await;
20674
20675    let diff_base = r#"
20676        use some::mod;
20677
20678        const A: u32 = 42;
20679
20680        fn main() {
20681            println!("hello");
20682
20683            println!("world");
20684        }
20685        "#
20686    .unindent();
20687
20688    cx.set_state(
20689        &r#"
20690        use some::modified;
20691
20692        ˇ
20693        fn main() {
20694            println!("hello there");
20695
20696            println!("around the");
20697            println!("world");
20698        }
20699        "#
20700        .unindent(),
20701    );
20702
20703    cx.set_head_text(&diff_base);
20704    executor.run_until_parked();
20705
20706    cx.update_editor(|editor, window, cx| {
20707        editor.go_to_next_hunk(&GoToHunk, window, cx);
20708        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20709    });
20710    executor.run_until_parked();
20711    cx.assert_state_with_diff(
20712        r#"
20713          use some::modified;
20714
20715
20716          fn main() {
20717        -     println!("hello");
20718        + ˇ    println!("hello there");
20719
20720              println!("around the");
20721              println!("world");
20722          }
20723        "#
20724        .unindent(),
20725    );
20726
20727    cx.update_editor(|editor, window, cx| {
20728        for _ in 0..2 {
20729            editor.go_to_next_hunk(&GoToHunk, window, cx);
20730            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20731        }
20732    });
20733    executor.run_until_parked();
20734    cx.assert_state_with_diff(
20735        r#"
20736        - use some::mod;
20737        + ˇuse some::modified;
20738
20739
20740          fn main() {
20741        -     println!("hello");
20742        +     println!("hello there");
20743
20744        +     println!("around the");
20745              println!("world");
20746          }
20747        "#
20748        .unindent(),
20749    );
20750
20751    cx.update_editor(|editor, window, cx| {
20752        editor.go_to_next_hunk(&GoToHunk, window, cx);
20753        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20754    });
20755    executor.run_until_parked();
20756    cx.assert_state_with_diff(
20757        r#"
20758        - use some::mod;
20759        + use some::modified;
20760
20761        - const A: u32 = 42;
20762          ˇ
20763          fn main() {
20764        -     println!("hello");
20765        +     println!("hello there");
20766
20767        +     println!("around the");
20768              println!("world");
20769          }
20770        "#
20771        .unindent(),
20772    );
20773
20774    cx.update_editor(|editor, window, cx| {
20775        editor.cancel(&Cancel, window, cx);
20776    });
20777
20778    cx.assert_state_with_diff(
20779        r#"
20780          use some::modified;
20781
20782          ˇ
20783          fn main() {
20784              println!("hello there");
20785
20786              println!("around the");
20787              println!("world");
20788          }
20789        "#
20790        .unindent(),
20791    );
20792}
20793
20794#[gpui::test]
20795async fn test_diff_base_change_with_expanded_diff_hunks(
20796    executor: BackgroundExecutor,
20797    cx: &mut TestAppContext,
20798) {
20799    init_test(cx, |_| {});
20800
20801    let mut cx = EditorTestContext::new(cx).await;
20802
20803    let diff_base = r#"
20804        use some::mod1;
20805        use some::mod2;
20806
20807        const A: u32 = 42;
20808        const B: u32 = 42;
20809        const C: u32 = 42;
20810
20811        fn main() {
20812            println!("hello");
20813
20814            println!("world");
20815        }
20816        "#
20817    .unindent();
20818
20819    cx.set_state(
20820        &r#"
20821        use some::mod2;
20822
20823        const A: u32 = 42;
20824        const C: u32 = 42;
20825
20826        fn main(ˇ) {
20827            //println!("hello");
20828
20829            println!("world");
20830            //
20831            //
20832        }
20833        "#
20834        .unindent(),
20835    );
20836
20837    cx.set_head_text(&diff_base);
20838    executor.run_until_parked();
20839
20840    cx.update_editor(|editor, window, cx| {
20841        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20842    });
20843    executor.run_until_parked();
20844    cx.assert_state_with_diff(
20845        r#"
20846        - use some::mod1;
20847          use some::mod2;
20848
20849          const A: u32 = 42;
20850        - const B: u32 = 42;
20851          const C: u32 = 42;
20852
20853          fn main(ˇ) {
20854        -     println!("hello");
20855        +     //println!("hello");
20856
20857              println!("world");
20858        +     //
20859        +     //
20860          }
20861        "#
20862        .unindent(),
20863    );
20864
20865    cx.set_head_text("new diff base!");
20866    executor.run_until_parked();
20867    cx.assert_state_with_diff(
20868        r#"
20869        - new diff base!
20870        + use some::mod2;
20871        +
20872        + const A: u32 = 42;
20873        + const C: u32 = 42;
20874        +
20875        + fn main(ˇ) {
20876        +     //println!("hello");
20877        +
20878        +     println!("world");
20879        +     //
20880        +     //
20881        + }
20882        "#
20883        .unindent(),
20884    );
20885}
20886
20887#[gpui::test]
20888async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20889    init_test(cx, |_| {});
20890
20891    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20892    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20893    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20894    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20895    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20896    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20897
20898    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20899    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20900    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20901
20902    let multi_buffer = cx.new(|cx| {
20903        let mut multibuffer = MultiBuffer::new(ReadWrite);
20904        multibuffer.push_excerpts(
20905            buffer_1.clone(),
20906            [
20907                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20908                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20909                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20910            ],
20911            cx,
20912        );
20913        multibuffer.push_excerpts(
20914            buffer_2.clone(),
20915            [
20916                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20917                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20918                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20919            ],
20920            cx,
20921        );
20922        multibuffer.push_excerpts(
20923            buffer_3.clone(),
20924            [
20925                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20926                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20927                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20928            ],
20929            cx,
20930        );
20931        multibuffer
20932    });
20933
20934    let editor =
20935        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20936    editor
20937        .update(cx, |editor, _window, cx| {
20938            for (buffer, diff_base) in [
20939                (buffer_1.clone(), file_1_old),
20940                (buffer_2.clone(), file_2_old),
20941                (buffer_3.clone(), file_3_old),
20942            ] {
20943                let diff = cx.new(|cx| {
20944                    BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20945                });
20946                editor
20947                    .buffer
20948                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20949            }
20950        })
20951        .unwrap();
20952
20953    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20954    cx.run_until_parked();
20955
20956    cx.assert_editor_state(
20957        &"
20958            ˇaaa
20959            ccc
20960            ddd
20961
20962            ggg
20963            hhh
20964
20965
20966            lll
20967            mmm
20968            NNN
20969
20970            qqq
20971            rrr
20972
20973            uuu
20974            111
20975            222
20976            333
20977
20978            666
20979            777
20980
20981            000
20982            !!!"
20983        .unindent(),
20984    );
20985
20986    cx.update_editor(|editor, window, cx| {
20987        editor.select_all(&SelectAll, window, cx);
20988        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20989    });
20990    cx.executor().run_until_parked();
20991
20992    cx.assert_state_with_diff(
20993        "
20994            «aaa
20995          - bbb
20996            ccc
20997            ddd
20998
20999            ggg
21000            hhh
21001
21002
21003            lll
21004            mmm
21005          - nnn
21006          + NNN
21007
21008            qqq
21009            rrr
21010
21011            uuu
21012            111
21013            222
21014            333
21015
21016          + 666
21017            777
21018
21019            000
21020            !!!ˇ»"
21021            .unindent(),
21022    );
21023}
21024
21025#[gpui::test]
21026async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
21027    init_test(cx, |_| {});
21028
21029    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
21030    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
21031
21032    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
21033    let multi_buffer = cx.new(|cx| {
21034        let mut multibuffer = MultiBuffer::new(ReadWrite);
21035        multibuffer.push_excerpts(
21036            buffer.clone(),
21037            [
21038                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
21039                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
21040                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
21041            ],
21042            cx,
21043        );
21044        multibuffer
21045    });
21046
21047    let editor =
21048        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
21049    editor
21050        .update(cx, |editor, _window, cx| {
21051            let diff = cx.new(|cx| {
21052                BufferDiff::new_with_base_text(base, &buffer.read(cx).text_snapshot(), cx)
21053            });
21054            editor
21055                .buffer
21056                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
21057        })
21058        .unwrap();
21059
21060    let mut cx = EditorTestContext::for_editor(editor, cx).await;
21061    cx.run_until_parked();
21062
21063    cx.update_editor(|editor, window, cx| {
21064        editor.expand_all_diff_hunks(&Default::default(), window, cx)
21065    });
21066    cx.executor().run_until_parked();
21067
21068    // When the start of a hunk coincides with the start of its excerpt,
21069    // the hunk is expanded. When the start of a hunk is earlier than
21070    // the start of its excerpt, the hunk is not expanded.
21071    cx.assert_state_with_diff(
21072        "
21073            ˇaaa
21074          - bbb
21075          + BBB
21076
21077          - ddd
21078          - eee
21079          + DDD
21080          + EEE
21081            fff
21082
21083            iii
21084        "
21085        .unindent(),
21086    );
21087}
21088
21089#[gpui::test]
21090async fn test_edits_around_expanded_insertion_hunks(
21091    executor: BackgroundExecutor,
21092    cx: &mut TestAppContext,
21093) {
21094    init_test(cx, |_| {});
21095
21096    let mut cx = EditorTestContext::new(cx).await;
21097
21098    let diff_base = r#"
21099        use some::mod1;
21100        use some::mod2;
21101
21102        const A: u32 = 42;
21103
21104        fn main() {
21105            println!("hello");
21106
21107            println!("world");
21108        }
21109        "#
21110    .unindent();
21111    executor.run_until_parked();
21112    cx.set_state(
21113        &r#"
21114        use some::mod1;
21115        use some::mod2;
21116
21117        const A: u32 = 42;
21118        const B: u32 = 42;
21119        const C: u32 = 42;
21120        ˇ
21121
21122        fn main() {
21123            println!("hello");
21124
21125            println!("world");
21126        }
21127        "#
21128        .unindent(),
21129    );
21130
21131    cx.set_head_text(&diff_base);
21132    executor.run_until_parked();
21133
21134    cx.update_editor(|editor, window, cx| {
21135        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21136    });
21137    executor.run_until_parked();
21138
21139    cx.assert_state_with_diff(
21140        r#"
21141        use some::mod1;
21142        use some::mod2;
21143
21144        const A: u32 = 42;
21145      + const B: u32 = 42;
21146      + const C: u32 = 42;
21147      + ˇ
21148
21149        fn main() {
21150            println!("hello");
21151
21152            println!("world");
21153        }
21154      "#
21155        .unindent(),
21156    );
21157
21158    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
21159    executor.run_until_parked();
21160
21161    cx.assert_state_with_diff(
21162        r#"
21163        use some::mod1;
21164        use some::mod2;
21165
21166        const A: u32 = 42;
21167      + const B: u32 = 42;
21168      + const C: u32 = 42;
21169      + const D: u32 = 42;
21170      + ˇ
21171
21172        fn main() {
21173            println!("hello");
21174
21175            println!("world");
21176        }
21177      "#
21178        .unindent(),
21179    );
21180
21181    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
21182    executor.run_until_parked();
21183
21184    cx.assert_state_with_diff(
21185        r#"
21186        use some::mod1;
21187        use some::mod2;
21188
21189        const A: u32 = 42;
21190      + const B: u32 = 42;
21191      + const C: u32 = 42;
21192      + const D: u32 = 42;
21193      + const E: u32 = 42;
21194      + ˇ
21195
21196        fn main() {
21197            println!("hello");
21198
21199            println!("world");
21200        }
21201      "#
21202        .unindent(),
21203    );
21204
21205    cx.update_editor(|editor, window, cx| {
21206        editor.delete_line(&DeleteLine, window, cx);
21207    });
21208    executor.run_until_parked();
21209
21210    cx.assert_state_with_diff(
21211        r#"
21212        use some::mod1;
21213        use some::mod2;
21214
21215        const A: u32 = 42;
21216      + const B: u32 = 42;
21217      + const C: u32 = 42;
21218      + const D: u32 = 42;
21219      + const E: u32 = 42;
21220        ˇ
21221        fn main() {
21222            println!("hello");
21223
21224            println!("world");
21225        }
21226      "#
21227        .unindent(),
21228    );
21229
21230    cx.update_editor(|editor, window, cx| {
21231        editor.move_up(&MoveUp, window, cx);
21232        editor.delete_line(&DeleteLine, window, cx);
21233        editor.move_up(&MoveUp, window, cx);
21234        editor.delete_line(&DeleteLine, window, cx);
21235        editor.move_up(&MoveUp, window, cx);
21236        editor.delete_line(&DeleteLine, window, cx);
21237    });
21238    executor.run_until_parked();
21239    cx.assert_state_with_diff(
21240        r#"
21241        use some::mod1;
21242        use some::mod2;
21243
21244        const A: u32 = 42;
21245      + const B: u32 = 42;
21246        ˇ
21247        fn main() {
21248            println!("hello");
21249
21250            println!("world");
21251        }
21252      "#
21253        .unindent(),
21254    );
21255
21256    cx.update_editor(|editor, window, cx| {
21257        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
21258        editor.delete_line(&DeleteLine, window, cx);
21259    });
21260    executor.run_until_parked();
21261    cx.assert_state_with_diff(
21262        r#"
21263        ˇ
21264        fn main() {
21265            println!("hello");
21266
21267            println!("world");
21268        }
21269      "#
21270        .unindent(),
21271    );
21272}
21273
21274#[gpui::test]
21275async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
21276    init_test(cx, |_| {});
21277
21278    let mut cx = EditorTestContext::new(cx).await;
21279    cx.set_head_text(indoc! { "
21280        one
21281        two
21282        three
21283        four
21284        five
21285        "
21286    });
21287    cx.set_state(indoc! { "
21288        one
21289        ˇthree
21290        five
21291    "});
21292    cx.run_until_parked();
21293    cx.update_editor(|editor, window, cx| {
21294        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21295    });
21296    cx.assert_state_with_diff(
21297        indoc! { "
21298        one
21299      - two
21300        ˇthree
21301      - four
21302        five
21303    "}
21304        .to_string(),
21305    );
21306    cx.update_editor(|editor, window, cx| {
21307        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21308    });
21309
21310    cx.assert_state_with_diff(
21311        indoc! { "
21312        one
21313        ˇthree
21314        five
21315    "}
21316        .to_string(),
21317    );
21318
21319    cx.update_editor(|editor, window, cx| {
21320        editor.move_up(&MoveUp, window, cx);
21321        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21322    });
21323    cx.assert_state_with_diff(
21324        indoc! { "
21325        ˇone
21326      - two
21327        three
21328        five
21329    "}
21330        .to_string(),
21331    );
21332
21333    cx.update_editor(|editor, window, cx| {
21334        editor.move_down(&MoveDown, window, cx);
21335        editor.move_down(&MoveDown, window, cx);
21336        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21337    });
21338    cx.assert_state_with_diff(
21339        indoc! { "
21340        one
21341      - two
21342        ˇthree
21343      - four
21344        five
21345    "}
21346        .to_string(),
21347    );
21348
21349    cx.set_state(indoc! { "
21350        one
21351        ˇTWO
21352        three
21353        four
21354        five
21355    "});
21356    cx.run_until_parked();
21357    cx.update_editor(|editor, window, cx| {
21358        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21359    });
21360
21361    cx.assert_state_with_diff(
21362        indoc! { "
21363            one
21364          - two
21365          + ˇTWO
21366            three
21367            four
21368            five
21369        "}
21370        .to_string(),
21371    );
21372    cx.update_editor(|editor, window, cx| {
21373        editor.move_up(&Default::default(), window, cx);
21374        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21375    });
21376    cx.assert_state_with_diff(
21377        indoc! { "
21378            one
21379            ˇTWO
21380            three
21381            four
21382            five
21383        "}
21384        .to_string(),
21385    );
21386}
21387
21388#[gpui::test]
21389async fn test_toggling_adjacent_diff_hunks_2(
21390    executor: BackgroundExecutor,
21391    cx: &mut TestAppContext,
21392) {
21393    init_test(cx, |_| {});
21394
21395    let mut cx = EditorTestContext::new(cx).await;
21396
21397    let diff_base = r#"
21398        lineA
21399        lineB
21400        lineC
21401        lineD
21402        "#
21403    .unindent();
21404
21405    cx.set_state(
21406        &r#"
21407        ˇlineA1
21408        lineB
21409        lineD
21410        "#
21411        .unindent(),
21412    );
21413    cx.set_head_text(&diff_base);
21414    executor.run_until_parked();
21415
21416    cx.update_editor(|editor, window, cx| {
21417        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21418    });
21419    executor.run_until_parked();
21420    cx.assert_state_with_diff(
21421        r#"
21422        - lineA
21423        + ˇlineA1
21424          lineB
21425          lineD
21426        "#
21427        .unindent(),
21428    );
21429
21430    cx.update_editor(|editor, window, cx| {
21431        editor.move_down(&MoveDown, window, cx);
21432        editor.move_right(&MoveRight, window, cx);
21433        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21434    });
21435    executor.run_until_parked();
21436    cx.assert_state_with_diff(
21437        r#"
21438        - lineA
21439        + lineA1
21440          lˇineB
21441        - lineC
21442          lineD
21443        "#
21444        .unindent(),
21445    );
21446}
21447
21448#[gpui::test]
21449async fn test_edits_around_expanded_deletion_hunks(
21450    executor: BackgroundExecutor,
21451    cx: &mut TestAppContext,
21452) {
21453    init_test(cx, |_| {});
21454
21455    let mut cx = EditorTestContext::new(cx).await;
21456
21457    let diff_base = r#"
21458        use some::mod1;
21459        use some::mod2;
21460
21461        const A: u32 = 42;
21462        const B: u32 = 42;
21463        const C: u32 = 42;
21464
21465
21466        fn main() {
21467            println!("hello");
21468
21469            println!("world");
21470        }
21471    "#
21472    .unindent();
21473    executor.run_until_parked();
21474    cx.set_state(
21475        &r#"
21476        use some::mod1;
21477        use some::mod2;
21478
21479        ˇconst B: u32 = 42;
21480        const C: u32 = 42;
21481
21482
21483        fn main() {
21484            println!("hello");
21485
21486            println!("world");
21487        }
21488        "#
21489        .unindent(),
21490    );
21491
21492    cx.set_head_text(&diff_base);
21493    executor.run_until_parked();
21494
21495    cx.update_editor(|editor, window, cx| {
21496        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21497    });
21498    executor.run_until_parked();
21499
21500    cx.assert_state_with_diff(
21501        r#"
21502        use some::mod1;
21503        use some::mod2;
21504
21505      - const A: u32 = 42;
21506        ˇconst B: u32 = 42;
21507        const C: u32 = 42;
21508
21509
21510        fn main() {
21511            println!("hello");
21512
21513            println!("world");
21514        }
21515      "#
21516        .unindent(),
21517    );
21518
21519    cx.update_editor(|editor, window, cx| {
21520        editor.delete_line(&DeleteLine, window, cx);
21521    });
21522    executor.run_until_parked();
21523    cx.assert_state_with_diff(
21524        r#"
21525        use some::mod1;
21526        use some::mod2;
21527
21528      - const A: u32 = 42;
21529      - const B: u32 = 42;
21530        ˇconst C: u32 = 42;
21531
21532
21533        fn main() {
21534            println!("hello");
21535
21536            println!("world");
21537        }
21538      "#
21539        .unindent(),
21540    );
21541
21542    cx.update_editor(|editor, window, cx| {
21543        editor.delete_line(&DeleteLine, window, cx);
21544    });
21545    executor.run_until_parked();
21546    cx.assert_state_with_diff(
21547        r#"
21548        use some::mod1;
21549        use some::mod2;
21550
21551      - const A: u32 = 42;
21552      - const B: u32 = 42;
21553      - const C: u32 = 42;
21554        ˇ
21555
21556        fn main() {
21557            println!("hello");
21558
21559            println!("world");
21560        }
21561      "#
21562        .unindent(),
21563    );
21564
21565    cx.update_editor(|editor, window, cx| {
21566        editor.handle_input("replacement", window, cx);
21567    });
21568    executor.run_until_parked();
21569    cx.assert_state_with_diff(
21570        r#"
21571        use some::mod1;
21572        use some::mod2;
21573
21574      - const A: u32 = 42;
21575      - const B: u32 = 42;
21576      - const C: u32 = 42;
21577      -
21578      + replacementˇ
21579
21580        fn main() {
21581            println!("hello");
21582
21583            println!("world");
21584        }
21585      "#
21586        .unindent(),
21587    );
21588}
21589
21590#[gpui::test]
21591async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21592    init_test(cx, |_| {});
21593
21594    let mut cx = EditorTestContext::new(cx).await;
21595
21596    let base_text = r#"
21597        one
21598        two
21599        three
21600        four
21601        five
21602    "#
21603    .unindent();
21604    executor.run_until_parked();
21605    cx.set_state(
21606        &r#"
21607        one
21608        two
21609        fˇour
21610        five
21611        "#
21612        .unindent(),
21613    );
21614
21615    cx.set_head_text(&base_text);
21616    executor.run_until_parked();
21617
21618    cx.update_editor(|editor, window, cx| {
21619        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21620    });
21621    executor.run_until_parked();
21622
21623    cx.assert_state_with_diff(
21624        r#"
21625          one
21626          two
21627        - three
21628          fˇour
21629          five
21630        "#
21631        .unindent(),
21632    );
21633
21634    cx.update_editor(|editor, window, cx| {
21635        editor.backspace(&Backspace, window, cx);
21636        editor.backspace(&Backspace, window, cx);
21637    });
21638    executor.run_until_parked();
21639    cx.assert_state_with_diff(
21640        r#"
21641          one
21642          two
21643        - threeˇ
21644        - four
21645        + our
21646          five
21647        "#
21648        .unindent(),
21649    );
21650}
21651
21652#[gpui::test]
21653async fn test_edit_after_expanded_modification_hunk(
21654    executor: BackgroundExecutor,
21655    cx: &mut TestAppContext,
21656) {
21657    init_test(cx, |_| {});
21658
21659    let mut cx = EditorTestContext::new(cx).await;
21660
21661    let diff_base = r#"
21662        use some::mod1;
21663        use some::mod2;
21664
21665        const A: u32 = 42;
21666        const B: u32 = 42;
21667        const C: u32 = 42;
21668        const D: u32 = 42;
21669
21670
21671        fn main() {
21672            println!("hello");
21673
21674            println!("world");
21675        }"#
21676    .unindent();
21677
21678    cx.set_state(
21679        &r#"
21680        use some::mod1;
21681        use some::mod2;
21682
21683        const A: u32 = 42;
21684        const B: u32 = 42;
21685        const C: u32 = 43ˇ
21686        const D: u32 = 42;
21687
21688
21689        fn main() {
21690            println!("hello");
21691
21692            println!("world");
21693        }"#
21694        .unindent(),
21695    );
21696
21697    cx.set_head_text(&diff_base);
21698    executor.run_until_parked();
21699    cx.update_editor(|editor, window, cx| {
21700        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21701    });
21702    executor.run_until_parked();
21703
21704    cx.assert_state_with_diff(
21705        r#"
21706        use some::mod1;
21707        use some::mod2;
21708
21709        const A: u32 = 42;
21710        const B: u32 = 42;
21711      - const C: u32 = 42;
21712      + const C: u32 = 43ˇ
21713        const D: u32 = 42;
21714
21715
21716        fn main() {
21717            println!("hello");
21718
21719            println!("world");
21720        }"#
21721        .unindent(),
21722    );
21723
21724    cx.update_editor(|editor, window, cx| {
21725        editor.handle_input("\nnew_line\n", window, cx);
21726    });
21727    executor.run_until_parked();
21728
21729    cx.assert_state_with_diff(
21730        r#"
21731        use some::mod1;
21732        use some::mod2;
21733
21734        const A: u32 = 42;
21735        const B: u32 = 42;
21736      - const C: u32 = 42;
21737      + const C: u32 = 43
21738      + new_line
21739      + ˇ
21740        const D: u32 = 42;
21741
21742
21743        fn main() {
21744            println!("hello");
21745
21746            println!("world");
21747        }"#
21748        .unindent(),
21749    );
21750}
21751
21752#[gpui::test]
21753async fn test_stage_and_unstage_added_file_hunk(
21754    executor: BackgroundExecutor,
21755    cx: &mut TestAppContext,
21756) {
21757    init_test(cx, |_| {});
21758
21759    let mut cx = EditorTestContext::new(cx).await;
21760    cx.update_editor(|editor, _, cx| {
21761        editor.set_expand_all_diff_hunks(cx);
21762    });
21763
21764    let working_copy = r#"
21765            ˇfn main() {
21766                println!("hello, world!");
21767            }
21768        "#
21769    .unindent();
21770
21771    cx.set_state(&working_copy);
21772    executor.run_until_parked();
21773
21774    cx.assert_state_with_diff(
21775        r#"
21776            + ˇfn main() {
21777            +     println!("hello, world!");
21778            + }
21779        "#
21780        .unindent(),
21781    );
21782    cx.assert_index_text(None);
21783
21784    cx.update_editor(|editor, window, cx| {
21785        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21786    });
21787    executor.run_until_parked();
21788    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21789    cx.assert_state_with_diff(
21790        r#"
21791            + ˇfn main() {
21792            +     println!("hello, world!");
21793            + }
21794        "#
21795        .unindent(),
21796    );
21797
21798    cx.update_editor(|editor, window, cx| {
21799        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21800    });
21801    executor.run_until_parked();
21802    cx.assert_index_text(None);
21803}
21804
21805async fn setup_indent_guides_editor(
21806    text: &str,
21807    cx: &mut TestAppContext,
21808) -> (BufferId, EditorTestContext) {
21809    init_test(cx, |_| {});
21810
21811    let mut cx = EditorTestContext::new(cx).await;
21812
21813    let buffer_id = cx.update_editor(|editor, window, cx| {
21814        editor.set_text(text, window, cx);
21815        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21816
21817        buffer_ids[0]
21818    });
21819
21820    (buffer_id, cx)
21821}
21822
21823fn assert_indent_guides(
21824    range: Range<u32>,
21825    expected: Vec<IndentGuide>,
21826    active_indices: Option<Vec<usize>>,
21827    cx: &mut EditorTestContext,
21828) {
21829    let indent_guides = cx.update_editor(|editor, window, cx| {
21830        let snapshot = editor.snapshot(window, cx).display_snapshot;
21831        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21832            editor,
21833            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21834            true,
21835            &snapshot,
21836            cx,
21837        );
21838
21839        indent_guides.sort_by(|a, b| {
21840            a.depth.cmp(&b.depth).then(
21841                a.start_row
21842                    .cmp(&b.start_row)
21843                    .then(a.end_row.cmp(&b.end_row)),
21844            )
21845        });
21846        indent_guides
21847    });
21848
21849    if let Some(expected) = active_indices {
21850        let active_indices = cx.update_editor(|editor, window, cx| {
21851            let snapshot = editor.snapshot(window, cx).display_snapshot;
21852            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21853        });
21854
21855        assert_eq!(
21856            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21857            expected,
21858            "Active indent guide indices do not match"
21859        );
21860    }
21861
21862    assert_eq!(indent_guides, expected, "Indent guides do not match");
21863}
21864
21865fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21866    IndentGuide {
21867        buffer_id,
21868        start_row: MultiBufferRow(start_row),
21869        end_row: MultiBufferRow(end_row),
21870        depth,
21871        tab_size: 4,
21872        settings: IndentGuideSettings {
21873            enabled: true,
21874            line_width: 1,
21875            active_line_width: 1,
21876            coloring: IndentGuideColoring::default(),
21877            background_coloring: IndentGuideBackgroundColoring::default(),
21878        },
21879    }
21880}
21881
21882#[gpui::test]
21883async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21884    let (buffer_id, mut cx) = setup_indent_guides_editor(
21885        &"
21886        fn main() {
21887            let a = 1;
21888        }"
21889        .unindent(),
21890        cx,
21891    )
21892    .await;
21893
21894    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21895}
21896
21897#[gpui::test]
21898async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21899    let (buffer_id, mut cx) = setup_indent_guides_editor(
21900        &"
21901        fn main() {
21902            let a = 1;
21903            let b = 2;
21904        }"
21905        .unindent(),
21906        cx,
21907    )
21908    .await;
21909
21910    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21911}
21912
21913#[gpui::test]
21914async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21915    let (buffer_id, mut cx) = setup_indent_guides_editor(
21916        &"
21917        fn main() {
21918            let a = 1;
21919            if a == 3 {
21920                let b = 2;
21921            } else {
21922                let c = 3;
21923            }
21924        }"
21925        .unindent(),
21926        cx,
21927    )
21928    .await;
21929
21930    assert_indent_guides(
21931        0..8,
21932        vec![
21933            indent_guide(buffer_id, 1, 6, 0),
21934            indent_guide(buffer_id, 3, 3, 1),
21935            indent_guide(buffer_id, 5, 5, 1),
21936        ],
21937        None,
21938        &mut cx,
21939    );
21940}
21941
21942#[gpui::test]
21943async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21944    let (buffer_id, mut cx) = setup_indent_guides_editor(
21945        &"
21946        fn main() {
21947            let a = 1;
21948                let b = 2;
21949            let c = 3;
21950        }"
21951        .unindent(),
21952        cx,
21953    )
21954    .await;
21955
21956    assert_indent_guides(
21957        0..5,
21958        vec![
21959            indent_guide(buffer_id, 1, 3, 0),
21960            indent_guide(buffer_id, 2, 2, 1),
21961        ],
21962        None,
21963        &mut cx,
21964    );
21965}
21966
21967#[gpui::test]
21968async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21969    let (buffer_id, mut cx) = setup_indent_guides_editor(
21970        &"
21971        fn main() {
21972            let a = 1;
21973
21974            let c = 3;
21975        }"
21976        .unindent(),
21977        cx,
21978    )
21979    .await;
21980
21981    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21982}
21983
21984#[gpui::test]
21985async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21986    let (buffer_id, mut cx) = setup_indent_guides_editor(
21987        &"
21988        fn main() {
21989            let a = 1;
21990
21991            let c = 3;
21992
21993            if a == 3 {
21994                let b = 2;
21995            } else {
21996                let c = 3;
21997            }
21998        }"
21999        .unindent(),
22000        cx,
22001    )
22002    .await;
22003
22004    assert_indent_guides(
22005        0..11,
22006        vec![
22007            indent_guide(buffer_id, 1, 9, 0),
22008            indent_guide(buffer_id, 6, 6, 1),
22009            indent_guide(buffer_id, 8, 8, 1),
22010        ],
22011        None,
22012        &mut cx,
22013    );
22014}
22015
22016#[gpui::test]
22017async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
22018    let (buffer_id, mut cx) = setup_indent_guides_editor(
22019        &"
22020        fn main() {
22021            let a = 1;
22022
22023            let c = 3;
22024
22025            if a == 3 {
22026                let b = 2;
22027            } else {
22028                let c = 3;
22029            }
22030        }"
22031        .unindent(),
22032        cx,
22033    )
22034    .await;
22035
22036    assert_indent_guides(
22037        1..11,
22038        vec![
22039            indent_guide(buffer_id, 1, 9, 0),
22040            indent_guide(buffer_id, 6, 6, 1),
22041            indent_guide(buffer_id, 8, 8, 1),
22042        ],
22043        None,
22044        &mut cx,
22045    );
22046}
22047
22048#[gpui::test]
22049async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
22050    let (buffer_id, mut cx) = setup_indent_guides_editor(
22051        &"
22052        fn main() {
22053            let a = 1;
22054
22055            let c = 3;
22056
22057            if a == 3 {
22058                let b = 2;
22059            } else {
22060                let c = 3;
22061            }
22062        }"
22063        .unindent(),
22064        cx,
22065    )
22066    .await;
22067
22068    assert_indent_guides(
22069        1..10,
22070        vec![
22071            indent_guide(buffer_id, 1, 9, 0),
22072            indent_guide(buffer_id, 6, 6, 1),
22073            indent_guide(buffer_id, 8, 8, 1),
22074        ],
22075        None,
22076        &mut cx,
22077    );
22078}
22079
22080#[gpui::test]
22081async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
22082    let (buffer_id, mut cx) = setup_indent_guides_editor(
22083        &"
22084        fn main() {
22085            if a {
22086                b(
22087                    c,
22088                    d,
22089                )
22090            } else {
22091                e(
22092                    f
22093                )
22094            }
22095        }"
22096        .unindent(),
22097        cx,
22098    )
22099    .await;
22100
22101    assert_indent_guides(
22102        0..11,
22103        vec![
22104            indent_guide(buffer_id, 1, 10, 0),
22105            indent_guide(buffer_id, 2, 5, 1),
22106            indent_guide(buffer_id, 7, 9, 1),
22107            indent_guide(buffer_id, 3, 4, 2),
22108            indent_guide(buffer_id, 8, 8, 2),
22109        ],
22110        None,
22111        &mut cx,
22112    );
22113
22114    cx.update_editor(|editor, window, cx| {
22115        editor.fold_at(MultiBufferRow(2), window, cx);
22116        assert_eq!(
22117            editor.display_text(cx),
22118            "
22119            fn main() {
22120                if a {
22121                    b(⋯
22122                    )
22123                } else {
22124                    e(
22125                        f
22126                    )
22127                }
22128            }"
22129            .unindent()
22130        );
22131    });
22132
22133    assert_indent_guides(
22134        0..11,
22135        vec![
22136            indent_guide(buffer_id, 1, 10, 0),
22137            indent_guide(buffer_id, 2, 5, 1),
22138            indent_guide(buffer_id, 7, 9, 1),
22139            indent_guide(buffer_id, 8, 8, 2),
22140        ],
22141        None,
22142        &mut cx,
22143    );
22144}
22145
22146#[gpui::test]
22147async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
22148    let (buffer_id, mut cx) = setup_indent_guides_editor(
22149        &"
22150        block1
22151            block2
22152                block3
22153                    block4
22154            block2
22155        block1
22156        block1"
22157            .unindent(),
22158        cx,
22159    )
22160    .await;
22161
22162    assert_indent_guides(
22163        1..10,
22164        vec![
22165            indent_guide(buffer_id, 1, 4, 0),
22166            indent_guide(buffer_id, 2, 3, 1),
22167            indent_guide(buffer_id, 3, 3, 2),
22168        ],
22169        None,
22170        &mut cx,
22171    );
22172}
22173
22174#[gpui::test]
22175async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
22176    let (buffer_id, mut cx) = setup_indent_guides_editor(
22177        &"
22178        block1
22179            block2
22180                block3
22181
22182        block1
22183        block1"
22184            .unindent(),
22185        cx,
22186    )
22187    .await;
22188
22189    assert_indent_guides(
22190        0..6,
22191        vec![
22192            indent_guide(buffer_id, 1, 2, 0),
22193            indent_guide(buffer_id, 2, 2, 1),
22194        ],
22195        None,
22196        &mut cx,
22197    );
22198}
22199
22200#[gpui::test]
22201async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
22202    let (buffer_id, mut cx) = setup_indent_guides_editor(
22203        &"
22204        function component() {
22205        \treturn (
22206        \t\t\t
22207        \t\t<div>
22208        \t\t\t<abc></abc>
22209        \t\t</div>
22210        \t)
22211        }"
22212        .unindent(),
22213        cx,
22214    )
22215    .await;
22216
22217    assert_indent_guides(
22218        0..8,
22219        vec![
22220            indent_guide(buffer_id, 1, 6, 0),
22221            indent_guide(buffer_id, 2, 5, 1),
22222            indent_guide(buffer_id, 4, 4, 2),
22223        ],
22224        None,
22225        &mut cx,
22226    );
22227}
22228
22229#[gpui::test]
22230async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
22231    let (buffer_id, mut cx) = setup_indent_guides_editor(
22232        &"
22233        function component() {
22234        \treturn (
22235        \t
22236        \t\t<div>
22237        \t\t\t<abc></abc>
22238        \t\t</div>
22239        \t)
22240        }"
22241        .unindent(),
22242        cx,
22243    )
22244    .await;
22245
22246    assert_indent_guides(
22247        0..8,
22248        vec![
22249            indent_guide(buffer_id, 1, 6, 0),
22250            indent_guide(buffer_id, 2, 5, 1),
22251            indent_guide(buffer_id, 4, 4, 2),
22252        ],
22253        None,
22254        &mut cx,
22255    );
22256}
22257
22258#[gpui::test]
22259async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
22260    let (buffer_id, mut cx) = setup_indent_guides_editor(
22261        &"
22262        block1
22263
22264
22265
22266            block2
22267        "
22268        .unindent(),
22269        cx,
22270    )
22271    .await;
22272
22273    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
22274}
22275
22276#[gpui::test]
22277async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
22278    let (buffer_id, mut cx) = setup_indent_guides_editor(
22279        &"
22280        def a:
22281        \tb = 3
22282        \tif True:
22283        \t\tc = 4
22284        \t\td = 5
22285        \tprint(b)
22286        "
22287        .unindent(),
22288        cx,
22289    )
22290    .await;
22291
22292    assert_indent_guides(
22293        0..6,
22294        vec![
22295            indent_guide(buffer_id, 1, 5, 0),
22296            indent_guide(buffer_id, 3, 4, 1),
22297        ],
22298        None,
22299        &mut cx,
22300    );
22301}
22302
22303#[gpui::test]
22304async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
22305    let (buffer_id, mut cx) = setup_indent_guides_editor(
22306        &"
22307    fn main() {
22308        let a = 1;
22309    }"
22310        .unindent(),
22311        cx,
22312    )
22313    .await;
22314
22315    cx.update_editor(|editor, window, cx| {
22316        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22317            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22318        });
22319    });
22320
22321    assert_indent_guides(
22322        0..3,
22323        vec![indent_guide(buffer_id, 1, 1, 0)],
22324        Some(vec![0]),
22325        &mut cx,
22326    );
22327}
22328
22329#[gpui::test]
22330async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
22331    let (buffer_id, mut cx) = setup_indent_guides_editor(
22332        &"
22333    fn main() {
22334        if 1 == 2 {
22335            let a = 1;
22336        }
22337    }"
22338        .unindent(),
22339        cx,
22340    )
22341    .await;
22342
22343    cx.update_editor(|editor, window, cx| {
22344        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22345            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22346        });
22347    });
22348    cx.run_until_parked();
22349
22350    assert_indent_guides(
22351        0..4,
22352        vec![
22353            indent_guide(buffer_id, 1, 3, 0),
22354            indent_guide(buffer_id, 2, 2, 1),
22355        ],
22356        Some(vec![1]),
22357        &mut cx,
22358    );
22359
22360    cx.update_editor(|editor, window, cx| {
22361        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22362            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22363        });
22364    });
22365    cx.run_until_parked();
22366
22367    assert_indent_guides(
22368        0..4,
22369        vec![
22370            indent_guide(buffer_id, 1, 3, 0),
22371            indent_guide(buffer_id, 2, 2, 1),
22372        ],
22373        Some(vec![1]),
22374        &mut cx,
22375    );
22376
22377    cx.update_editor(|editor, window, cx| {
22378        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22379            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
22380        });
22381    });
22382    cx.run_until_parked();
22383
22384    assert_indent_guides(
22385        0..4,
22386        vec![
22387            indent_guide(buffer_id, 1, 3, 0),
22388            indent_guide(buffer_id, 2, 2, 1),
22389        ],
22390        Some(vec![0]),
22391        &mut cx,
22392    );
22393}
22394
22395#[gpui::test]
22396async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
22397    let (buffer_id, mut cx) = setup_indent_guides_editor(
22398        &"
22399    fn main() {
22400        let a = 1;
22401
22402        let b = 2;
22403    }"
22404        .unindent(),
22405        cx,
22406    )
22407    .await;
22408
22409    cx.update_editor(|editor, window, cx| {
22410        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22411            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22412        });
22413    });
22414
22415    assert_indent_guides(
22416        0..5,
22417        vec![indent_guide(buffer_id, 1, 3, 0)],
22418        Some(vec![0]),
22419        &mut cx,
22420    );
22421}
22422
22423#[gpui::test]
22424async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
22425    let (buffer_id, mut cx) = setup_indent_guides_editor(
22426        &"
22427    def m:
22428        a = 1
22429        pass"
22430            .unindent(),
22431        cx,
22432    )
22433    .await;
22434
22435    cx.update_editor(|editor, window, cx| {
22436        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22437            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22438        });
22439    });
22440
22441    assert_indent_guides(
22442        0..3,
22443        vec![indent_guide(buffer_id, 1, 2, 0)],
22444        Some(vec![0]),
22445        &mut cx,
22446    );
22447}
22448
22449#[gpui::test]
22450async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
22451    init_test(cx, |_| {});
22452    let mut cx = EditorTestContext::new(cx).await;
22453    let text = indoc! {
22454        "
22455        impl A {
22456            fn b() {
22457                0;
22458                3;
22459                5;
22460                6;
22461                7;
22462            }
22463        }
22464        "
22465    };
22466    let base_text = indoc! {
22467        "
22468        impl A {
22469            fn b() {
22470                0;
22471                1;
22472                2;
22473                3;
22474                4;
22475            }
22476            fn c() {
22477                5;
22478                6;
22479                7;
22480            }
22481        }
22482        "
22483    };
22484
22485    cx.update_editor(|editor, window, cx| {
22486        editor.set_text(text, window, cx);
22487
22488        editor.buffer().update(cx, |multibuffer, cx| {
22489            let buffer = multibuffer.as_singleton().unwrap();
22490            let diff = cx.new(|cx| {
22491                BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
22492            });
22493
22494            multibuffer.set_all_diff_hunks_expanded(cx);
22495            multibuffer.add_diff(diff, cx);
22496
22497            buffer.read(cx).remote_id()
22498        })
22499    });
22500    cx.run_until_parked();
22501
22502    cx.assert_state_with_diff(
22503        indoc! { "
22504          impl A {
22505              fn b() {
22506                  0;
22507        -         1;
22508        -         2;
22509                  3;
22510        -         4;
22511        -     }
22512        -     fn c() {
22513                  5;
22514                  6;
22515                  7;
22516              }
22517          }
22518          ˇ"
22519        }
22520        .to_string(),
22521    );
22522
22523    let mut actual_guides = cx.update_editor(|editor, window, cx| {
22524        editor
22525            .snapshot(window, cx)
22526            .buffer_snapshot()
22527            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
22528            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
22529            .collect::<Vec<_>>()
22530    });
22531    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
22532    assert_eq!(
22533        actual_guides,
22534        vec![
22535            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
22536            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
22537            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
22538        ]
22539    );
22540}
22541
22542#[gpui::test]
22543async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22544    init_test(cx, |_| {});
22545    let mut cx = EditorTestContext::new(cx).await;
22546
22547    let diff_base = r#"
22548        a
22549        b
22550        c
22551        "#
22552    .unindent();
22553
22554    cx.set_state(
22555        &r#"
22556        ˇA
22557        b
22558        C
22559        "#
22560        .unindent(),
22561    );
22562    cx.set_head_text(&diff_base);
22563    cx.update_editor(|editor, window, cx| {
22564        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22565    });
22566    executor.run_until_parked();
22567
22568    let both_hunks_expanded = r#"
22569        - a
22570        + ˇA
22571          b
22572        - c
22573        + C
22574        "#
22575    .unindent();
22576
22577    cx.assert_state_with_diff(both_hunks_expanded.clone());
22578
22579    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22580        let snapshot = editor.snapshot(window, cx);
22581        let hunks = editor
22582            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22583            .collect::<Vec<_>>();
22584        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22585        hunks
22586            .into_iter()
22587            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22588            .collect::<Vec<_>>()
22589    });
22590    assert_eq!(hunk_ranges.len(), 2);
22591
22592    cx.update_editor(|editor, _, cx| {
22593        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22594    });
22595    executor.run_until_parked();
22596
22597    let second_hunk_expanded = r#"
22598          ˇA
22599          b
22600        - c
22601        + C
22602        "#
22603    .unindent();
22604
22605    cx.assert_state_with_diff(second_hunk_expanded);
22606
22607    cx.update_editor(|editor, _, cx| {
22608        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22609    });
22610    executor.run_until_parked();
22611
22612    cx.assert_state_with_diff(both_hunks_expanded.clone());
22613
22614    cx.update_editor(|editor, _, cx| {
22615        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22616    });
22617    executor.run_until_parked();
22618
22619    let first_hunk_expanded = r#"
22620        - a
22621        + ˇA
22622          b
22623          C
22624        "#
22625    .unindent();
22626
22627    cx.assert_state_with_diff(first_hunk_expanded);
22628
22629    cx.update_editor(|editor, _, cx| {
22630        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22631    });
22632    executor.run_until_parked();
22633
22634    cx.assert_state_with_diff(both_hunks_expanded);
22635
22636    cx.set_state(
22637        &r#"
22638        ˇA
22639        b
22640        "#
22641        .unindent(),
22642    );
22643    cx.run_until_parked();
22644
22645    // TODO this cursor position seems bad
22646    cx.assert_state_with_diff(
22647        r#"
22648        - ˇa
22649        + A
22650          b
22651        "#
22652        .unindent(),
22653    );
22654
22655    cx.update_editor(|editor, window, cx| {
22656        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22657    });
22658
22659    cx.assert_state_with_diff(
22660        r#"
22661            - ˇa
22662            + A
22663              b
22664            - c
22665            "#
22666        .unindent(),
22667    );
22668
22669    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22670        let snapshot = editor.snapshot(window, cx);
22671        let hunks = editor
22672            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22673            .collect::<Vec<_>>();
22674        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22675        hunks
22676            .into_iter()
22677            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22678            .collect::<Vec<_>>()
22679    });
22680    assert_eq!(hunk_ranges.len(), 2);
22681
22682    cx.update_editor(|editor, _, cx| {
22683        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22684    });
22685    executor.run_until_parked();
22686
22687    cx.assert_state_with_diff(
22688        r#"
22689        - ˇa
22690        + A
22691          b
22692        "#
22693        .unindent(),
22694    );
22695}
22696
22697#[gpui::test]
22698async fn test_toggle_deletion_hunk_at_start_of_file(
22699    executor: BackgroundExecutor,
22700    cx: &mut TestAppContext,
22701) {
22702    init_test(cx, |_| {});
22703    let mut cx = EditorTestContext::new(cx).await;
22704
22705    let diff_base = r#"
22706        a
22707        b
22708        c
22709        "#
22710    .unindent();
22711
22712    cx.set_state(
22713        &r#"
22714        ˇb
22715        c
22716        "#
22717        .unindent(),
22718    );
22719    cx.set_head_text(&diff_base);
22720    cx.update_editor(|editor, window, cx| {
22721        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22722    });
22723    executor.run_until_parked();
22724
22725    let hunk_expanded = r#"
22726        - a
22727          ˇb
22728          c
22729        "#
22730    .unindent();
22731
22732    cx.assert_state_with_diff(hunk_expanded.clone());
22733
22734    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22735        let snapshot = editor.snapshot(window, cx);
22736        let hunks = editor
22737            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22738            .collect::<Vec<_>>();
22739        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22740        hunks
22741            .into_iter()
22742            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22743            .collect::<Vec<_>>()
22744    });
22745    assert_eq!(hunk_ranges.len(), 1);
22746
22747    cx.update_editor(|editor, _, cx| {
22748        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22749    });
22750    executor.run_until_parked();
22751
22752    let hunk_collapsed = r#"
22753          ˇb
22754          c
22755        "#
22756    .unindent();
22757
22758    cx.assert_state_with_diff(hunk_collapsed);
22759
22760    cx.update_editor(|editor, _, cx| {
22761        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22762    });
22763    executor.run_until_parked();
22764
22765    cx.assert_state_with_diff(hunk_expanded);
22766}
22767
22768#[gpui::test]
22769async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
22770    executor: BackgroundExecutor,
22771    cx: &mut TestAppContext,
22772) {
22773    init_test(cx, |_| {});
22774    let mut cx = EditorTestContext::new(cx).await;
22775
22776    cx.set_state("ˇnew\nsecond\nthird\n");
22777    cx.set_head_text("old\nsecond\nthird\n");
22778    cx.update_editor(|editor, window, cx| {
22779        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
22780    });
22781    executor.run_until_parked();
22782    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22783
22784    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
22785    cx.update_editor(|editor, window, cx| {
22786        let snapshot = editor.snapshot(window, cx);
22787        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22788        let hunks = editor
22789            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22790            .collect::<Vec<_>>();
22791        assert_eq!(hunks.len(), 1);
22792        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
22793        editor.toggle_single_diff_hunk(hunk_range, cx)
22794    });
22795    executor.run_until_parked();
22796    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
22797
22798    // Keep the editor scrolled to the top so the full hunk remains visible.
22799    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22800}
22801
22802#[gpui::test]
22803async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22804    init_test(cx, |_| {});
22805
22806    let fs = FakeFs::new(cx.executor());
22807    fs.insert_tree(
22808        path!("/test"),
22809        json!({
22810            ".git": {},
22811            "file-1": "ONE\n",
22812            "file-2": "TWO\n",
22813            "file-3": "THREE\n",
22814        }),
22815    )
22816    .await;
22817
22818    fs.set_head_for_repo(
22819        path!("/test/.git").as_ref(),
22820        &[
22821            ("file-1", "one\n".into()),
22822            ("file-2", "two\n".into()),
22823            ("file-3", "three\n".into()),
22824        ],
22825        "deadbeef",
22826    );
22827
22828    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22829    let mut buffers = vec![];
22830    for i in 1..=3 {
22831        let buffer = project
22832            .update(cx, |project, cx| {
22833                let path = format!(path!("/test/file-{}"), i);
22834                project.open_local_buffer(path, cx)
22835            })
22836            .await
22837            .unwrap();
22838        buffers.push(buffer);
22839    }
22840
22841    let multibuffer = cx.new(|cx| {
22842        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22843        multibuffer.set_all_diff_hunks_expanded(cx);
22844        for buffer in &buffers {
22845            let snapshot = buffer.read(cx).snapshot();
22846            multibuffer.set_excerpts_for_path(
22847                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22848                buffer.clone(),
22849                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22850                2,
22851                cx,
22852            );
22853        }
22854        multibuffer
22855    });
22856
22857    let editor = cx.add_window(|window, cx| {
22858        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22859    });
22860    cx.run_until_parked();
22861
22862    let snapshot = editor
22863        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22864        .unwrap();
22865    let hunks = snapshot
22866        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22867        .map(|hunk| match hunk {
22868            DisplayDiffHunk::Unfolded {
22869                display_row_range, ..
22870            } => display_row_range,
22871            DisplayDiffHunk::Folded { .. } => unreachable!(),
22872        })
22873        .collect::<Vec<_>>();
22874    assert_eq!(
22875        hunks,
22876        [
22877            DisplayRow(2)..DisplayRow(4),
22878            DisplayRow(7)..DisplayRow(9),
22879            DisplayRow(12)..DisplayRow(14),
22880        ]
22881    );
22882}
22883
22884#[gpui::test]
22885async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22886    init_test(cx, |_| {});
22887
22888    let mut cx = EditorTestContext::new(cx).await;
22889    cx.set_head_text(indoc! { "
22890        one
22891        two
22892        three
22893        four
22894        five
22895        "
22896    });
22897    cx.set_index_text(indoc! { "
22898        one
22899        two
22900        three
22901        four
22902        five
22903        "
22904    });
22905    cx.set_state(indoc! {"
22906        one
22907        TWO
22908        ˇTHREE
22909        FOUR
22910        five
22911    "});
22912    cx.run_until_parked();
22913    cx.update_editor(|editor, window, cx| {
22914        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22915    });
22916    cx.run_until_parked();
22917    cx.assert_index_text(Some(indoc! {"
22918        one
22919        TWO
22920        THREE
22921        FOUR
22922        five
22923    "}));
22924    cx.set_state(indoc! { "
22925        one
22926        TWO
22927        ˇTHREE-HUNDRED
22928        FOUR
22929        five
22930    "});
22931    cx.run_until_parked();
22932    cx.update_editor(|editor, window, cx| {
22933        let snapshot = editor.snapshot(window, cx);
22934        let hunks = editor
22935            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22936            .collect::<Vec<_>>();
22937        assert_eq!(hunks.len(), 1);
22938        assert_eq!(
22939            hunks[0].status(),
22940            DiffHunkStatus {
22941                kind: DiffHunkStatusKind::Modified,
22942                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22943            }
22944        );
22945
22946        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22947    });
22948    cx.run_until_parked();
22949    cx.assert_index_text(Some(indoc! {"
22950        one
22951        TWO
22952        THREE-HUNDRED
22953        FOUR
22954        five
22955    "}));
22956}
22957
22958#[gpui::test]
22959fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22960    init_test(cx, |_| {});
22961
22962    let editor = cx.add_window(|window, cx| {
22963        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22964        build_editor(buffer, window, cx)
22965    });
22966
22967    let render_args = Arc::new(Mutex::new(None));
22968    let snapshot = editor
22969        .update(cx, |editor, window, cx| {
22970            let snapshot = editor.buffer().read(cx).snapshot(cx);
22971            let range =
22972                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22973
22974            struct RenderArgs {
22975                row: MultiBufferRow,
22976                folded: bool,
22977                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22978            }
22979
22980            let crease = Crease::inline(
22981                range,
22982                FoldPlaceholder::test(),
22983                {
22984                    let toggle_callback = render_args.clone();
22985                    move |row, folded, callback, _window, _cx| {
22986                        *toggle_callback.lock() = Some(RenderArgs {
22987                            row,
22988                            folded,
22989                            callback,
22990                        });
22991                        div()
22992                    }
22993                },
22994                |_row, _folded, _window, _cx| div(),
22995            );
22996
22997            editor.insert_creases(Some(crease), cx);
22998            let snapshot = editor.snapshot(window, cx);
22999            let _div =
23000                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
23001            snapshot
23002        })
23003        .unwrap();
23004
23005    let render_args = render_args.lock().take().unwrap();
23006    assert_eq!(render_args.row, MultiBufferRow(1));
23007    assert!(!render_args.folded);
23008    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
23009
23010    cx.update_window(*editor, |_, window, cx| {
23011        (render_args.callback)(true, window, cx)
23012    })
23013    .unwrap();
23014    let snapshot = editor
23015        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
23016        .unwrap();
23017    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
23018
23019    cx.update_window(*editor, |_, window, cx| {
23020        (render_args.callback)(false, window, cx)
23021    })
23022    .unwrap();
23023    let snapshot = editor
23024        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
23025        .unwrap();
23026    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
23027}
23028
23029#[gpui::test]
23030async fn test_input_text(cx: &mut TestAppContext) {
23031    init_test(cx, |_| {});
23032    let mut cx = EditorTestContext::new(cx).await;
23033
23034    cx.set_state(
23035        &r#"ˇone
23036        two
23037
23038        three
23039        fourˇ
23040        five
23041
23042        siˇx"#
23043            .unindent(),
23044    );
23045
23046    cx.dispatch_action(HandleInput(String::new()));
23047    cx.assert_editor_state(
23048        &r#"ˇone
23049        two
23050
23051        three
23052        fourˇ
23053        five
23054
23055        siˇx"#
23056            .unindent(),
23057    );
23058
23059    cx.dispatch_action(HandleInput("AAAA".to_string()));
23060    cx.assert_editor_state(
23061        &r#"AAAAˇone
23062        two
23063
23064        three
23065        fourAAAAˇ
23066        five
23067
23068        siAAAAˇx"#
23069            .unindent(),
23070    );
23071}
23072
23073#[gpui::test]
23074async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
23075    init_test(cx, |_| {});
23076
23077    let mut cx = EditorTestContext::new(cx).await;
23078    cx.set_state(
23079        r#"let foo = 1;
23080let foo = 2;
23081let foo = 3;
23082let fooˇ = 4;
23083let foo = 5;
23084let foo = 6;
23085let foo = 7;
23086let foo = 8;
23087let foo = 9;
23088let foo = 10;
23089let foo = 11;
23090let foo = 12;
23091let foo = 13;
23092let foo = 14;
23093let foo = 15;"#,
23094    );
23095
23096    cx.update_editor(|e, window, cx| {
23097        assert_eq!(
23098            e.next_scroll_position,
23099            NextScrollCursorCenterTopBottom::Center,
23100            "Default next scroll direction is center",
23101        );
23102
23103        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23104        assert_eq!(
23105            e.next_scroll_position,
23106            NextScrollCursorCenterTopBottom::Top,
23107            "After center, next scroll direction should be top",
23108        );
23109
23110        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23111        assert_eq!(
23112            e.next_scroll_position,
23113            NextScrollCursorCenterTopBottom::Bottom,
23114            "After top, next scroll direction should be bottom",
23115        );
23116
23117        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23118        assert_eq!(
23119            e.next_scroll_position,
23120            NextScrollCursorCenterTopBottom::Center,
23121            "After bottom, scrolling should start over",
23122        );
23123
23124        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23125        assert_eq!(
23126            e.next_scroll_position,
23127            NextScrollCursorCenterTopBottom::Top,
23128            "Scrolling continues if retriggered fast enough"
23129        );
23130    });
23131
23132    cx.executor()
23133        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
23134    cx.executor().run_until_parked();
23135    cx.update_editor(|e, _, _| {
23136        assert_eq!(
23137            e.next_scroll_position,
23138            NextScrollCursorCenterTopBottom::Center,
23139            "If scrolling is not triggered fast enough, it should reset"
23140        );
23141    });
23142}
23143
23144#[gpui::test]
23145async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
23146    init_test(cx, |_| {});
23147    let mut cx = EditorLspTestContext::new_rust(
23148        lsp::ServerCapabilities {
23149            definition_provider: Some(lsp::OneOf::Left(true)),
23150            references_provider: Some(lsp::OneOf::Left(true)),
23151            ..lsp::ServerCapabilities::default()
23152        },
23153        cx,
23154    )
23155    .await;
23156
23157    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
23158        let go_to_definition = cx
23159            .lsp
23160            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
23161                move |params, _| async move {
23162                    if empty_go_to_definition {
23163                        Ok(None)
23164                    } else {
23165                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
23166                            uri: params.text_document_position_params.text_document.uri,
23167                            range: lsp::Range::new(
23168                                lsp::Position::new(4, 3),
23169                                lsp::Position::new(4, 6),
23170                            ),
23171                        })))
23172                    }
23173                },
23174            );
23175        let references = cx
23176            .lsp
23177            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23178                Ok(Some(vec![lsp::Location {
23179                    uri: params.text_document_position.text_document.uri,
23180                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
23181                }]))
23182            });
23183        (go_to_definition, references)
23184    };
23185
23186    cx.set_state(
23187        &r#"fn one() {
23188            let mut a = ˇtwo();
23189        }
23190
23191        fn two() {}"#
23192            .unindent(),
23193    );
23194    set_up_lsp_handlers(false, &mut cx);
23195    let navigated = cx
23196        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23197        .await
23198        .expect("Failed to navigate to definition");
23199    assert_eq!(
23200        navigated,
23201        Navigated::Yes,
23202        "Should have navigated to definition from the GetDefinition response"
23203    );
23204    cx.assert_editor_state(
23205        &r#"fn one() {
23206            let mut a = two();
23207        }
23208
23209        fn «twoˇ»() {}"#
23210            .unindent(),
23211    );
23212
23213    let editors = cx.update_workspace(|workspace, _, cx| {
23214        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23215    });
23216    cx.update_editor(|_, _, test_editor_cx| {
23217        assert_eq!(
23218            editors.len(),
23219            1,
23220            "Initially, only one, test, editor should be open in the workspace"
23221        );
23222        assert_eq!(
23223            test_editor_cx.entity(),
23224            editors.last().expect("Asserted len is 1").clone()
23225        );
23226    });
23227
23228    set_up_lsp_handlers(true, &mut cx);
23229    let navigated = cx
23230        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23231        .await
23232        .expect("Failed to navigate to lookup references");
23233    assert_eq!(
23234        navigated,
23235        Navigated::Yes,
23236        "Should have navigated to references as a fallback after empty GoToDefinition response"
23237    );
23238    // We should not change the selections in the existing file,
23239    // if opening another milti buffer with the references
23240    cx.assert_editor_state(
23241        &r#"fn one() {
23242            let mut a = two();
23243        }
23244
23245        fn «twoˇ»() {}"#
23246            .unindent(),
23247    );
23248    let editors = cx.update_workspace(|workspace, _, cx| {
23249        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23250    });
23251    cx.update_editor(|_, _, test_editor_cx| {
23252        assert_eq!(
23253            editors.len(),
23254            2,
23255            "After falling back to references search, we open a new editor with the results"
23256        );
23257        let references_fallback_text = editors
23258            .into_iter()
23259            .find(|new_editor| *new_editor != test_editor_cx.entity())
23260            .expect("Should have one non-test editor now")
23261            .read(test_editor_cx)
23262            .text(test_editor_cx);
23263        assert_eq!(
23264            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
23265            "Should use the range from the references response and not the GoToDefinition one"
23266        );
23267    });
23268}
23269
23270#[gpui::test]
23271async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
23272    init_test(cx, |_| {});
23273    cx.update(|cx| {
23274        let mut editor_settings = EditorSettings::get_global(cx).clone();
23275        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
23276        EditorSettings::override_global(editor_settings, cx);
23277    });
23278    let mut cx = EditorLspTestContext::new_rust(
23279        lsp::ServerCapabilities {
23280            definition_provider: Some(lsp::OneOf::Left(true)),
23281            references_provider: Some(lsp::OneOf::Left(true)),
23282            ..lsp::ServerCapabilities::default()
23283        },
23284        cx,
23285    )
23286    .await;
23287    let original_state = r#"fn one() {
23288        let mut a = ˇtwo();
23289    }
23290
23291    fn two() {}"#
23292        .unindent();
23293    cx.set_state(&original_state);
23294
23295    let mut go_to_definition = cx
23296        .lsp
23297        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
23298            move |_, _| async move { Ok(None) },
23299        );
23300    let _references = cx
23301        .lsp
23302        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
23303            panic!("Should not call for references with no go to definition fallback")
23304        });
23305
23306    let navigated = cx
23307        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23308        .await
23309        .expect("Failed to navigate to lookup references");
23310    go_to_definition
23311        .next()
23312        .await
23313        .expect("Should have called the go_to_definition handler");
23314
23315    assert_eq!(
23316        navigated,
23317        Navigated::No,
23318        "Should have navigated to references as a fallback after empty GoToDefinition response"
23319    );
23320    cx.assert_editor_state(&original_state);
23321    let editors = cx.update_workspace(|workspace, _, cx| {
23322        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23323    });
23324    cx.update_editor(|_, _, _| {
23325        assert_eq!(
23326            editors.len(),
23327            1,
23328            "After unsuccessful fallback, no other editor should have been opened"
23329        );
23330    });
23331}
23332
23333#[gpui::test]
23334async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
23335    init_test(cx, |_| {});
23336    let mut cx = EditorLspTestContext::new_rust(
23337        lsp::ServerCapabilities {
23338            references_provider: Some(lsp::OneOf::Left(true)),
23339            ..lsp::ServerCapabilities::default()
23340        },
23341        cx,
23342    )
23343    .await;
23344
23345    cx.set_state(
23346        &r#"
23347        fn one() {
23348            let mut a = two();
23349        }
23350
23351        fn ˇtwo() {}"#
23352            .unindent(),
23353    );
23354    cx.lsp
23355        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23356            Ok(Some(vec![
23357                lsp::Location {
23358                    uri: params.text_document_position.text_document.uri.clone(),
23359                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23360                },
23361                lsp::Location {
23362                    uri: params.text_document_position.text_document.uri,
23363                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
23364                },
23365            ]))
23366        });
23367    let navigated = cx
23368        .update_editor(|editor, window, cx| {
23369            editor.find_all_references(&FindAllReferences::default(), window, cx)
23370        })
23371        .unwrap()
23372        .await
23373        .expect("Failed to navigate to references");
23374    assert_eq!(
23375        navigated,
23376        Navigated::Yes,
23377        "Should have navigated to references from the FindAllReferences response"
23378    );
23379    cx.assert_editor_state(
23380        &r#"fn one() {
23381            let mut a = two();
23382        }
23383
23384        fn ˇtwo() {}"#
23385            .unindent(),
23386    );
23387
23388    let editors = cx.update_workspace(|workspace, _, cx| {
23389        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23390    });
23391    cx.update_editor(|_, _, _| {
23392        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
23393    });
23394
23395    cx.set_state(
23396        &r#"fn one() {
23397            let mut a = ˇtwo();
23398        }
23399
23400        fn two() {}"#
23401            .unindent(),
23402    );
23403    let navigated = cx
23404        .update_editor(|editor, window, cx| {
23405            editor.find_all_references(&FindAllReferences::default(), window, cx)
23406        })
23407        .unwrap()
23408        .await
23409        .expect("Failed to navigate to references");
23410    assert_eq!(
23411        navigated,
23412        Navigated::Yes,
23413        "Should have navigated to references from the FindAllReferences response"
23414    );
23415    cx.assert_editor_state(
23416        &r#"fn one() {
23417            let mut a = ˇtwo();
23418        }
23419
23420        fn two() {}"#
23421            .unindent(),
23422    );
23423    let editors = cx.update_workspace(|workspace, _, cx| {
23424        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23425    });
23426    cx.update_editor(|_, _, _| {
23427        assert_eq!(
23428            editors.len(),
23429            2,
23430            "should have re-used the previous multibuffer"
23431        );
23432    });
23433
23434    cx.set_state(
23435        &r#"fn one() {
23436            let mut a = ˇtwo();
23437        }
23438        fn three() {}
23439        fn two() {}"#
23440            .unindent(),
23441    );
23442    cx.lsp
23443        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23444            Ok(Some(vec![
23445                lsp::Location {
23446                    uri: params.text_document_position.text_document.uri.clone(),
23447                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23448                },
23449                lsp::Location {
23450                    uri: params.text_document_position.text_document.uri,
23451                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
23452                },
23453            ]))
23454        });
23455    let navigated = cx
23456        .update_editor(|editor, window, cx| {
23457            editor.find_all_references(&FindAllReferences::default(), window, cx)
23458        })
23459        .unwrap()
23460        .await
23461        .expect("Failed to navigate to references");
23462    assert_eq!(
23463        navigated,
23464        Navigated::Yes,
23465        "Should have navigated to references from the FindAllReferences response"
23466    );
23467    cx.assert_editor_state(
23468        &r#"fn one() {
23469                let mut a = ˇtwo();
23470            }
23471            fn three() {}
23472            fn two() {}"#
23473            .unindent(),
23474    );
23475    let editors = cx.update_workspace(|workspace, _, cx| {
23476        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23477    });
23478    cx.update_editor(|_, _, _| {
23479        assert_eq!(
23480            editors.len(),
23481            3,
23482            "should have used a new multibuffer as offsets changed"
23483        );
23484    });
23485}
23486#[gpui::test]
23487async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
23488    init_test(cx, |_| {});
23489
23490    let language = Arc::new(Language::new(
23491        LanguageConfig::default(),
23492        Some(tree_sitter_rust::LANGUAGE.into()),
23493    ));
23494
23495    let text = r#"
23496        #[cfg(test)]
23497        mod tests() {
23498            #[test]
23499            fn runnable_1() {
23500                let a = 1;
23501            }
23502
23503            #[test]
23504            fn runnable_2() {
23505                let a = 1;
23506                let b = 2;
23507            }
23508        }
23509    "#
23510    .unindent();
23511
23512    let fs = FakeFs::new(cx.executor());
23513    fs.insert_file("/file.rs", Default::default()).await;
23514
23515    let project = Project::test(fs, ["/a".as_ref()], cx).await;
23516    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23517    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23518    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
23519    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
23520
23521    let editor = cx.new_window_entity(|window, cx| {
23522        Editor::new(
23523            EditorMode::full(),
23524            multi_buffer,
23525            Some(project.clone()),
23526            window,
23527            cx,
23528        )
23529    });
23530
23531    editor.update_in(cx, |editor, window, cx| {
23532        let snapshot = editor.buffer().read(cx).snapshot(cx);
23533        editor.tasks.insert(
23534            (buffer.read(cx).remote_id(), 3),
23535            RunnableTasks {
23536                templates: vec![],
23537                offset: snapshot.anchor_before(MultiBufferOffset(43)),
23538                column: 0,
23539                extra_variables: HashMap::default(),
23540                context_range: BufferOffset(43)..BufferOffset(85),
23541            },
23542        );
23543        editor.tasks.insert(
23544            (buffer.read(cx).remote_id(), 8),
23545            RunnableTasks {
23546                templates: vec![],
23547                offset: snapshot.anchor_before(MultiBufferOffset(86)),
23548                column: 0,
23549                extra_variables: HashMap::default(),
23550                context_range: BufferOffset(86)..BufferOffset(191),
23551            },
23552        );
23553
23554        // Test finding task when cursor is inside function body
23555        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23556            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
23557        });
23558        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23559        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
23560
23561        // Test finding task when cursor is on function name
23562        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23563            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
23564        });
23565        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23566        assert_eq!(row, 8, "Should find task when cursor is on function name");
23567    });
23568}
23569
23570#[gpui::test]
23571async fn test_folding_buffers(cx: &mut TestAppContext) {
23572    init_test(cx, |_| {});
23573
23574    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23575    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
23576    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
23577
23578    let fs = FakeFs::new(cx.executor());
23579    fs.insert_tree(
23580        path!("/a"),
23581        json!({
23582            "first.rs": sample_text_1,
23583            "second.rs": sample_text_2,
23584            "third.rs": sample_text_3,
23585        }),
23586    )
23587    .await;
23588    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23589    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23590    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23591    let worktree = project.update(cx, |project, cx| {
23592        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23593        assert_eq!(worktrees.len(), 1);
23594        worktrees.pop().unwrap()
23595    });
23596    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23597
23598    let buffer_1 = project
23599        .update(cx, |project, cx| {
23600            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23601        })
23602        .await
23603        .unwrap();
23604    let buffer_2 = project
23605        .update(cx, |project, cx| {
23606            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23607        })
23608        .await
23609        .unwrap();
23610    let buffer_3 = project
23611        .update(cx, |project, cx| {
23612            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23613        })
23614        .await
23615        .unwrap();
23616
23617    let multi_buffer = cx.new(|cx| {
23618        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23619        multi_buffer.push_excerpts(
23620            buffer_1.clone(),
23621            [
23622                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23623                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23624                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23625            ],
23626            cx,
23627        );
23628        multi_buffer.push_excerpts(
23629            buffer_2.clone(),
23630            [
23631                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23632                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23633                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23634            ],
23635            cx,
23636        );
23637        multi_buffer.push_excerpts(
23638            buffer_3.clone(),
23639            [
23640                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23641                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23642                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23643            ],
23644            cx,
23645        );
23646        multi_buffer
23647    });
23648    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23649        Editor::new(
23650            EditorMode::full(),
23651            multi_buffer.clone(),
23652            Some(project.clone()),
23653            window,
23654            cx,
23655        )
23656    });
23657
23658    assert_eq!(
23659        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23660        "\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",
23661    );
23662
23663    multi_buffer_editor.update(cx, |editor, cx| {
23664        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23665    });
23666    assert_eq!(
23667        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23668        "\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",
23669        "After folding the first buffer, its text should not be displayed"
23670    );
23671
23672    multi_buffer_editor.update(cx, |editor, cx| {
23673        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23674    });
23675    assert_eq!(
23676        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23677        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
23678        "After folding the second buffer, its text should not be displayed"
23679    );
23680
23681    multi_buffer_editor.update(cx, |editor, cx| {
23682        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23683    });
23684    assert_eq!(
23685        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23686        "\n\n\n\n\n",
23687        "After folding the third buffer, its text should not be displayed"
23688    );
23689
23690    // Emulate selection inside the fold logic, that should work
23691    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23692        editor
23693            .snapshot(window, cx)
23694            .next_line_boundary(Point::new(0, 4));
23695    });
23696
23697    multi_buffer_editor.update(cx, |editor, cx| {
23698        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23699    });
23700    assert_eq!(
23701        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23702        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23703        "After unfolding the second buffer, its text should be displayed"
23704    );
23705
23706    // Typing inside of buffer 1 causes that buffer to be unfolded.
23707    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23708        assert_eq!(
23709            multi_buffer
23710                .read(cx)
23711                .snapshot(cx)
23712                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
23713                .collect::<String>(),
23714            "bbbb"
23715        );
23716        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23717            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
23718        });
23719        editor.handle_input("B", window, cx);
23720    });
23721
23722    assert_eq!(
23723        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23724        "\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",
23725        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
23726    );
23727
23728    multi_buffer_editor.update(cx, |editor, cx| {
23729        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23730    });
23731    assert_eq!(
23732        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23733        "\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",
23734        "After unfolding the all buffers, all original text should be displayed"
23735    );
23736}
23737
23738#[gpui::test]
23739async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
23740    init_test(cx, |_| {});
23741
23742    let sample_text_1 = "1111\n2222\n3333".to_string();
23743    let sample_text_2 = "4444\n5555\n6666".to_string();
23744    let sample_text_3 = "7777\n8888\n9999".to_string();
23745
23746    let fs = FakeFs::new(cx.executor());
23747    fs.insert_tree(
23748        path!("/a"),
23749        json!({
23750            "first.rs": sample_text_1,
23751            "second.rs": sample_text_2,
23752            "third.rs": sample_text_3,
23753        }),
23754    )
23755    .await;
23756    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23757    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23758    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23759    let worktree = project.update(cx, |project, cx| {
23760        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23761        assert_eq!(worktrees.len(), 1);
23762        worktrees.pop().unwrap()
23763    });
23764    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23765
23766    let buffer_1 = project
23767        .update(cx, |project, cx| {
23768            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23769        })
23770        .await
23771        .unwrap();
23772    let buffer_2 = project
23773        .update(cx, |project, cx| {
23774            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23775        })
23776        .await
23777        .unwrap();
23778    let buffer_3 = project
23779        .update(cx, |project, cx| {
23780            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23781        })
23782        .await
23783        .unwrap();
23784
23785    let multi_buffer = cx.new(|cx| {
23786        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23787        multi_buffer.push_excerpts(
23788            buffer_1.clone(),
23789            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23790            cx,
23791        );
23792        multi_buffer.push_excerpts(
23793            buffer_2.clone(),
23794            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23795            cx,
23796        );
23797        multi_buffer.push_excerpts(
23798            buffer_3.clone(),
23799            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23800            cx,
23801        );
23802        multi_buffer
23803    });
23804
23805    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23806        Editor::new(
23807            EditorMode::full(),
23808            multi_buffer,
23809            Some(project.clone()),
23810            window,
23811            cx,
23812        )
23813    });
23814
23815    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23816    assert_eq!(
23817        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23818        full_text,
23819    );
23820
23821    multi_buffer_editor.update(cx, |editor, cx| {
23822        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23823    });
23824    assert_eq!(
23825        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23826        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23827        "After folding the first buffer, its text should not be displayed"
23828    );
23829
23830    multi_buffer_editor.update(cx, |editor, cx| {
23831        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23832    });
23833
23834    assert_eq!(
23835        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23836        "\n\n\n\n\n\n7777\n8888\n9999",
23837        "After folding the second buffer, its text should not be displayed"
23838    );
23839
23840    multi_buffer_editor.update(cx, |editor, cx| {
23841        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23842    });
23843    assert_eq!(
23844        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23845        "\n\n\n\n\n",
23846        "After folding the third buffer, its text should not be displayed"
23847    );
23848
23849    multi_buffer_editor.update(cx, |editor, cx| {
23850        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23851    });
23852    assert_eq!(
23853        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23854        "\n\n\n\n4444\n5555\n6666\n\n",
23855        "After unfolding the second buffer, its text should be displayed"
23856    );
23857
23858    multi_buffer_editor.update(cx, |editor, cx| {
23859        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23860    });
23861    assert_eq!(
23862        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23863        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23864        "After unfolding the first buffer, its text should be displayed"
23865    );
23866
23867    multi_buffer_editor.update(cx, |editor, cx| {
23868        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23869    });
23870    assert_eq!(
23871        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23872        full_text,
23873        "After unfolding all buffers, all original text should be displayed"
23874    );
23875}
23876
23877#[gpui::test]
23878async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23879    init_test(cx, |_| {});
23880
23881    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23882
23883    let fs = FakeFs::new(cx.executor());
23884    fs.insert_tree(
23885        path!("/a"),
23886        json!({
23887            "main.rs": sample_text,
23888        }),
23889    )
23890    .await;
23891    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23892    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23893    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23894    let worktree = project.update(cx, |project, cx| {
23895        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23896        assert_eq!(worktrees.len(), 1);
23897        worktrees.pop().unwrap()
23898    });
23899    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23900
23901    let buffer_1 = project
23902        .update(cx, |project, cx| {
23903            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23904        })
23905        .await
23906        .unwrap();
23907
23908    let multi_buffer = cx.new(|cx| {
23909        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23910        multi_buffer.push_excerpts(
23911            buffer_1.clone(),
23912            [ExcerptRange::new(
23913                Point::new(0, 0)
23914                    ..Point::new(
23915                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23916                        0,
23917                    ),
23918            )],
23919            cx,
23920        );
23921        multi_buffer
23922    });
23923    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23924        Editor::new(
23925            EditorMode::full(),
23926            multi_buffer,
23927            Some(project.clone()),
23928            window,
23929            cx,
23930        )
23931    });
23932
23933    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23934    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23935        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23936        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23937        editor.highlight_text(
23938            HighlightKey::Editor,
23939            vec![highlight_range.clone()],
23940            HighlightStyle::color(Hsla::green()),
23941            cx,
23942        );
23943        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23944            s.select_ranges(Some(highlight_range))
23945        });
23946    });
23947
23948    let full_text = format!("\n\n{sample_text}");
23949    assert_eq!(
23950        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23951        full_text,
23952    );
23953}
23954
23955#[gpui::test]
23956async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23957    init_test(cx, |_| {});
23958    cx.update(|cx| {
23959        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23960            "keymaps/default-linux.json",
23961            cx,
23962        )
23963        .unwrap();
23964        cx.bind_keys(default_key_bindings);
23965    });
23966
23967    let (editor, cx) = cx.add_window_view(|window, cx| {
23968        let multi_buffer = MultiBuffer::build_multi(
23969            [
23970                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23971                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23972                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23973                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23974            ],
23975            cx,
23976        );
23977        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23978
23979        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23980        // fold all but the second buffer, so that we test navigating between two
23981        // adjacent folded buffers, as well as folded buffers at the start and
23982        // end the multibuffer
23983        editor.fold_buffer(buffer_ids[0], cx);
23984        editor.fold_buffer(buffer_ids[2], cx);
23985        editor.fold_buffer(buffer_ids[3], cx);
23986
23987        editor
23988    });
23989    cx.simulate_resize(size(px(1000.), px(1000.)));
23990
23991    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23992    cx.assert_excerpts_with_selections(indoc! {"
23993        [EXCERPT]
23994        ˇ[FOLDED]
23995        [EXCERPT]
23996        a1
23997        b1
23998        [EXCERPT]
23999        [FOLDED]
24000        [EXCERPT]
24001        [FOLDED]
24002        "
24003    });
24004    cx.simulate_keystroke("down");
24005    cx.assert_excerpts_with_selections(indoc! {"
24006        [EXCERPT]
24007        [FOLDED]
24008        [EXCERPT]
24009        ˇa1
24010        b1
24011        [EXCERPT]
24012        [FOLDED]
24013        [EXCERPT]
24014        [FOLDED]
24015        "
24016    });
24017    cx.simulate_keystroke("down");
24018    cx.assert_excerpts_with_selections(indoc! {"
24019        [EXCERPT]
24020        [FOLDED]
24021        [EXCERPT]
24022        a1
24023        ˇb1
24024        [EXCERPT]
24025        [FOLDED]
24026        [EXCERPT]
24027        [FOLDED]
24028        "
24029    });
24030    cx.simulate_keystroke("down");
24031    cx.assert_excerpts_with_selections(indoc! {"
24032        [EXCERPT]
24033        [FOLDED]
24034        [EXCERPT]
24035        a1
24036        b1
24037        ˇ[EXCERPT]
24038        [FOLDED]
24039        [EXCERPT]
24040        [FOLDED]
24041        "
24042    });
24043    cx.simulate_keystroke("down");
24044    cx.assert_excerpts_with_selections(indoc! {"
24045        [EXCERPT]
24046        [FOLDED]
24047        [EXCERPT]
24048        a1
24049        b1
24050        [EXCERPT]
24051        ˇ[FOLDED]
24052        [EXCERPT]
24053        [FOLDED]
24054        "
24055    });
24056    for _ in 0..5 {
24057        cx.simulate_keystroke("down");
24058        cx.assert_excerpts_with_selections(indoc! {"
24059            [EXCERPT]
24060            [FOLDED]
24061            [EXCERPT]
24062            a1
24063            b1
24064            [EXCERPT]
24065            [FOLDED]
24066            [EXCERPT]
24067            ˇ[FOLDED]
24068            "
24069        });
24070    }
24071
24072    cx.simulate_keystroke("up");
24073    cx.assert_excerpts_with_selections(indoc! {"
24074        [EXCERPT]
24075        [FOLDED]
24076        [EXCERPT]
24077        a1
24078        b1
24079        [EXCERPT]
24080        ˇ[FOLDED]
24081        [EXCERPT]
24082        [FOLDED]
24083        "
24084    });
24085    cx.simulate_keystroke("up");
24086    cx.assert_excerpts_with_selections(indoc! {"
24087        [EXCERPT]
24088        [FOLDED]
24089        [EXCERPT]
24090        a1
24091        b1
24092        ˇ[EXCERPT]
24093        [FOLDED]
24094        [EXCERPT]
24095        [FOLDED]
24096        "
24097    });
24098    cx.simulate_keystroke("up");
24099    cx.assert_excerpts_with_selections(indoc! {"
24100        [EXCERPT]
24101        [FOLDED]
24102        [EXCERPT]
24103        a1
24104        ˇb1
24105        [EXCERPT]
24106        [FOLDED]
24107        [EXCERPT]
24108        [FOLDED]
24109        "
24110    });
24111    cx.simulate_keystroke("up");
24112    cx.assert_excerpts_with_selections(indoc! {"
24113        [EXCERPT]
24114        [FOLDED]
24115        [EXCERPT]
24116        ˇa1
24117        b1
24118        [EXCERPT]
24119        [FOLDED]
24120        [EXCERPT]
24121        [FOLDED]
24122        "
24123    });
24124    for _ in 0..5 {
24125        cx.simulate_keystroke("up");
24126        cx.assert_excerpts_with_selections(indoc! {"
24127            [EXCERPT]
24128            ˇ[FOLDED]
24129            [EXCERPT]
24130            a1
24131            b1
24132            [EXCERPT]
24133            [FOLDED]
24134            [EXCERPT]
24135            [FOLDED]
24136            "
24137        });
24138    }
24139}
24140
24141#[gpui::test]
24142async fn test_edit_prediction_text(cx: &mut TestAppContext) {
24143    init_test(cx, |_| {});
24144
24145    // Simple insertion
24146    assert_highlighted_edits(
24147        "Hello, world!",
24148        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
24149        true,
24150        cx,
24151        |highlighted_edits, cx| {
24152            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
24153            assert_eq!(highlighted_edits.highlights.len(), 1);
24154            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
24155            assert_eq!(
24156                highlighted_edits.highlights[0].1.background_color,
24157                Some(cx.theme().status().created_background)
24158            );
24159        },
24160    )
24161    .await;
24162
24163    // Replacement
24164    assert_highlighted_edits(
24165        "This is a test.",
24166        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
24167        false,
24168        cx,
24169        |highlighted_edits, cx| {
24170            assert_eq!(highlighted_edits.text, "That is a test.");
24171            assert_eq!(highlighted_edits.highlights.len(), 1);
24172            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
24173            assert_eq!(
24174                highlighted_edits.highlights[0].1.background_color,
24175                Some(cx.theme().status().created_background)
24176            );
24177        },
24178    )
24179    .await;
24180
24181    // Multiple edits
24182    assert_highlighted_edits(
24183        "Hello, world!",
24184        vec![
24185            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
24186            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
24187        ],
24188        false,
24189        cx,
24190        |highlighted_edits, cx| {
24191            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
24192            assert_eq!(highlighted_edits.highlights.len(), 2);
24193            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
24194            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
24195            assert_eq!(
24196                highlighted_edits.highlights[0].1.background_color,
24197                Some(cx.theme().status().created_background)
24198            );
24199            assert_eq!(
24200                highlighted_edits.highlights[1].1.background_color,
24201                Some(cx.theme().status().created_background)
24202            );
24203        },
24204    )
24205    .await;
24206
24207    // Multiple lines with edits
24208    assert_highlighted_edits(
24209        "First line\nSecond line\nThird line\nFourth line",
24210        vec![
24211            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
24212            (
24213                Point::new(2, 0)..Point::new(2, 10),
24214                "New third line".to_string(),
24215            ),
24216            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
24217        ],
24218        false,
24219        cx,
24220        |highlighted_edits, cx| {
24221            assert_eq!(
24222                highlighted_edits.text,
24223                "Second modified\nNew third line\nFourth updated line"
24224            );
24225            assert_eq!(highlighted_edits.highlights.len(), 3);
24226            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
24227            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
24228            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
24229            for highlight in &highlighted_edits.highlights {
24230                assert_eq!(
24231                    highlight.1.background_color,
24232                    Some(cx.theme().status().created_background)
24233                );
24234            }
24235        },
24236    )
24237    .await;
24238}
24239
24240#[gpui::test]
24241async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
24242    init_test(cx, |_| {});
24243
24244    // Deletion
24245    assert_highlighted_edits(
24246        "Hello, world!",
24247        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
24248        true,
24249        cx,
24250        |highlighted_edits, cx| {
24251            assert_eq!(highlighted_edits.text, "Hello, world!");
24252            assert_eq!(highlighted_edits.highlights.len(), 1);
24253            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
24254            assert_eq!(
24255                highlighted_edits.highlights[0].1.background_color,
24256                Some(cx.theme().status().deleted_background)
24257            );
24258        },
24259    )
24260    .await;
24261
24262    // Insertion
24263    assert_highlighted_edits(
24264        "Hello, world!",
24265        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
24266        true,
24267        cx,
24268        |highlighted_edits, cx| {
24269            assert_eq!(highlighted_edits.highlights.len(), 1);
24270            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
24271            assert_eq!(
24272                highlighted_edits.highlights[0].1.background_color,
24273                Some(cx.theme().status().created_background)
24274            );
24275        },
24276    )
24277    .await;
24278}
24279
24280async fn assert_highlighted_edits(
24281    text: &str,
24282    edits: Vec<(Range<Point>, String)>,
24283    include_deletions: bool,
24284    cx: &mut TestAppContext,
24285    assertion_fn: impl Fn(HighlightedText, &App),
24286) {
24287    let window = cx.add_window(|window, cx| {
24288        let buffer = MultiBuffer::build_simple(text, cx);
24289        Editor::new(EditorMode::full(), buffer, None, window, cx)
24290    });
24291    let cx = &mut VisualTestContext::from_window(*window, cx);
24292
24293    let (buffer, snapshot) = window
24294        .update(cx, |editor, _window, cx| {
24295            (
24296                editor.buffer().clone(),
24297                editor.buffer().read(cx).snapshot(cx),
24298            )
24299        })
24300        .unwrap();
24301
24302    let edits = edits
24303        .into_iter()
24304        .map(|(range, edit)| {
24305            (
24306                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
24307                edit,
24308            )
24309        })
24310        .collect::<Vec<_>>();
24311
24312    let text_anchor_edits = edits
24313        .clone()
24314        .into_iter()
24315        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
24316        .collect::<Vec<_>>();
24317
24318    let edit_preview = window
24319        .update(cx, |_, _window, cx| {
24320            buffer
24321                .read(cx)
24322                .as_singleton()
24323                .unwrap()
24324                .read(cx)
24325                .preview_edits(text_anchor_edits.into(), cx)
24326        })
24327        .unwrap()
24328        .await;
24329
24330    cx.update(|_window, cx| {
24331        let highlighted_edits = edit_prediction_edit_text(
24332            snapshot.as_singleton().unwrap().2,
24333            &edits,
24334            &edit_preview,
24335            include_deletions,
24336            cx,
24337        );
24338        assertion_fn(highlighted_edits, cx)
24339    });
24340}
24341
24342#[track_caller]
24343fn assert_breakpoint(
24344    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
24345    path: &Arc<Path>,
24346    expected: Vec<(u32, Breakpoint)>,
24347) {
24348    if expected.is_empty() {
24349        assert!(!breakpoints.contains_key(path), "{}", path.display());
24350    } else {
24351        let mut breakpoint = breakpoints
24352            .get(path)
24353            .unwrap()
24354            .iter()
24355            .map(|breakpoint| {
24356                (
24357                    breakpoint.row,
24358                    Breakpoint {
24359                        message: breakpoint.message.clone(),
24360                        state: breakpoint.state,
24361                        condition: breakpoint.condition.clone(),
24362                        hit_condition: breakpoint.hit_condition.clone(),
24363                    },
24364                )
24365            })
24366            .collect::<Vec<_>>();
24367
24368        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
24369
24370        assert_eq!(expected, breakpoint);
24371    }
24372}
24373
24374fn add_log_breakpoint_at_cursor(
24375    editor: &mut Editor,
24376    log_message: &str,
24377    window: &mut Window,
24378    cx: &mut Context<Editor>,
24379) {
24380    let (anchor, bp) = editor
24381        .breakpoints_at_cursors(window, cx)
24382        .first()
24383        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
24384        .unwrap_or_else(|| {
24385            let snapshot = editor.snapshot(window, cx);
24386            let cursor_position: Point =
24387                editor.selections.newest(&snapshot.display_snapshot).head();
24388
24389            let breakpoint_position = snapshot
24390                .buffer_snapshot()
24391                .anchor_before(Point::new(cursor_position.row, 0));
24392
24393            (breakpoint_position, Breakpoint::new_log(log_message))
24394        });
24395
24396    editor.edit_breakpoint_at_anchor(
24397        anchor,
24398        bp,
24399        BreakpointEditAction::EditLogMessage(log_message.into()),
24400        cx,
24401    );
24402}
24403
24404#[gpui::test]
24405async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
24406    init_test(cx, |_| {});
24407
24408    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24409    let fs = FakeFs::new(cx.executor());
24410    fs.insert_tree(
24411        path!("/a"),
24412        json!({
24413            "main.rs": sample_text,
24414        }),
24415    )
24416    .await;
24417    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24418    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24419    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24420
24421    let fs = FakeFs::new(cx.executor());
24422    fs.insert_tree(
24423        path!("/a"),
24424        json!({
24425            "main.rs": sample_text,
24426        }),
24427    )
24428    .await;
24429    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24430    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24431    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24432    let worktree_id = workspace
24433        .update(cx, |workspace, _window, cx| {
24434            workspace.project().update(cx, |project, cx| {
24435                project.worktrees(cx).next().unwrap().read(cx).id()
24436            })
24437        })
24438        .unwrap();
24439
24440    let buffer = project
24441        .update(cx, |project, cx| {
24442            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24443        })
24444        .await
24445        .unwrap();
24446
24447    let (editor, cx) = cx.add_window_view(|window, cx| {
24448        Editor::new(
24449            EditorMode::full(),
24450            MultiBuffer::build_from_buffer(buffer, cx),
24451            Some(project.clone()),
24452            window,
24453            cx,
24454        )
24455    });
24456
24457    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24458    let abs_path = project.read_with(cx, |project, cx| {
24459        project
24460            .absolute_path(&project_path, cx)
24461            .map(Arc::from)
24462            .unwrap()
24463    });
24464
24465    // assert we can add breakpoint on the first line
24466    editor.update_in(cx, |editor, window, cx| {
24467        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24468        editor.move_to_end(&MoveToEnd, window, cx);
24469        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24470    });
24471
24472    let breakpoints = editor.update(cx, |editor, cx| {
24473        editor
24474            .breakpoint_store()
24475            .as_ref()
24476            .unwrap()
24477            .read(cx)
24478            .all_source_breakpoints(cx)
24479    });
24480
24481    assert_eq!(1, breakpoints.len());
24482    assert_breakpoint(
24483        &breakpoints,
24484        &abs_path,
24485        vec![
24486            (0, Breakpoint::new_standard()),
24487            (3, Breakpoint::new_standard()),
24488        ],
24489    );
24490
24491    editor.update_in(cx, |editor, window, cx| {
24492        editor.move_to_beginning(&MoveToBeginning, window, cx);
24493        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24494    });
24495
24496    let breakpoints = editor.update(cx, |editor, cx| {
24497        editor
24498            .breakpoint_store()
24499            .as_ref()
24500            .unwrap()
24501            .read(cx)
24502            .all_source_breakpoints(cx)
24503    });
24504
24505    assert_eq!(1, breakpoints.len());
24506    assert_breakpoint(
24507        &breakpoints,
24508        &abs_path,
24509        vec![(3, Breakpoint::new_standard())],
24510    );
24511
24512    editor.update_in(cx, |editor, window, cx| {
24513        editor.move_to_end(&MoveToEnd, window, cx);
24514        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24515    });
24516
24517    let breakpoints = editor.update(cx, |editor, cx| {
24518        editor
24519            .breakpoint_store()
24520            .as_ref()
24521            .unwrap()
24522            .read(cx)
24523            .all_source_breakpoints(cx)
24524    });
24525
24526    assert_eq!(0, breakpoints.len());
24527    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24528}
24529
24530#[gpui::test]
24531async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
24532    init_test(cx, |_| {});
24533
24534    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24535
24536    let fs = FakeFs::new(cx.executor());
24537    fs.insert_tree(
24538        path!("/a"),
24539        json!({
24540            "main.rs": sample_text,
24541        }),
24542    )
24543    .await;
24544    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24545    let (workspace, cx) =
24546        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24547
24548    let worktree_id = workspace.update(cx, |workspace, cx| {
24549        workspace.project().update(cx, |project, cx| {
24550            project.worktrees(cx).next().unwrap().read(cx).id()
24551        })
24552    });
24553
24554    let buffer = project
24555        .update(cx, |project, cx| {
24556            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24557        })
24558        .await
24559        .unwrap();
24560
24561    let (editor, cx) = cx.add_window_view(|window, cx| {
24562        Editor::new(
24563            EditorMode::full(),
24564            MultiBuffer::build_from_buffer(buffer, cx),
24565            Some(project.clone()),
24566            window,
24567            cx,
24568        )
24569    });
24570
24571    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24572    let abs_path = project.read_with(cx, |project, cx| {
24573        project
24574            .absolute_path(&project_path, cx)
24575            .map(Arc::from)
24576            .unwrap()
24577    });
24578
24579    editor.update_in(cx, |editor, window, cx| {
24580        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24581    });
24582
24583    let breakpoints = editor.update(cx, |editor, cx| {
24584        editor
24585            .breakpoint_store()
24586            .as_ref()
24587            .unwrap()
24588            .read(cx)
24589            .all_source_breakpoints(cx)
24590    });
24591
24592    assert_breakpoint(
24593        &breakpoints,
24594        &abs_path,
24595        vec![(0, Breakpoint::new_log("hello world"))],
24596    );
24597
24598    // Removing a log message from a log breakpoint should remove it
24599    editor.update_in(cx, |editor, window, cx| {
24600        add_log_breakpoint_at_cursor(editor, "", window, cx);
24601    });
24602
24603    let breakpoints = editor.update(cx, |editor, cx| {
24604        editor
24605            .breakpoint_store()
24606            .as_ref()
24607            .unwrap()
24608            .read(cx)
24609            .all_source_breakpoints(cx)
24610    });
24611
24612    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24613
24614    editor.update_in(cx, |editor, window, cx| {
24615        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24616        editor.move_to_end(&MoveToEnd, window, cx);
24617        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24618        // Not adding a log message to a standard breakpoint shouldn't remove it
24619        add_log_breakpoint_at_cursor(editor, "", window, cx);
24620    });
24621
24622    let breakpoints = editor.update(cx, |editor, cx| {
24623        editor
24624            .breakpoint_store()
24625            .as_ref()
24626            .unwrap()
24627            .read(cx)
24628            .all_source_breakpoints(cx)
24629    });
24630
24631    assert_breakpoint(
24632        &breakpoints,
24633        &abs_path,
24634        vec![
24635            (0, Breakpoint::new_standard()),
24636            (3, Breakpoint::new_standard()),
24637        ],
24638    );
24639
24640    editor.update_in(cx, |editor, window, cx| {
24641        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24642    });
24643
24644    let breakpoints = editor.update(cx, |editor, cx| {
24645        editor
24646            .breakpoint_store()
24647            .as_ref()
24648            .unwrap()
24649            .read(cx)
24650            .all_source_breakpoints(cx)
24651    });
24652
24653    assert_breakpoint(
24654        &breakpoints,
24655        &abs_path,
24656        vec![
24657            (0, Breakpoint::new_standard()),
24658            (3, Breakpoint::new_log("hello world")),
24659        ],
24660    );
24661
24662    editor.update_in(cx, |editor, window, cx| {
24663        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
24664    });
24665
24666    let breakpoints = editor.update(cx, |editor, cx| {
24667        editor
24668            .breakpoint_store()
24669            .as_ref()
24670            .unwrap()
24671            .read(cx)
24672            .all_source_breakpoints(cx)
24673    });
24674
24675    assert_breakpoint(
24676        &breakpoints,
24677        &abs_path,
24678        vec![
24679            (0, Breakpoint::new_standard()),
24680            (3, Breakpoint::new_log("hello Earth!!")),
24681        ],
24682    );
24683}
24684
24685/// This also tests that Editor::breakpoint_at_cursor_head is working properly
24686/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
24687/// or when breakpoints were placed out of order. This tests for a regression too
24688#[gpui::test]
24689async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
24690    init_test(cx, |_| {});
24691
24692    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24693    let fs = FakeFs::new(cx.executor());
24694    fs.insert_tree(
24695        path!("/a"),
24696        json!({
24697            "main.rs": sample_text,
24698        }),
24699    )
24700    .await;
24701    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24702    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24703    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24704
24705    let fs = FakeFs::new(cx.executor());
24706    fs.insert_tree(
24707        path!("/a"),
24708        json!({
24709            "main.rs": sample_text,
24710        }),
24711    )
24712    .await;
24713    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24714    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24715    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24716    let worktree_id = workspace
24717        .update(cx, |workspace, _window, cx| {
24718            workspace.project().update(cx, |project, cx| {
24719                project.worktrees(cx).next().unwrap().read(cx).id()
24720            })
24721        })
24722        .unwrap();
24723
24724    let buffer = project
24725        .update(cx, |project, cx| {
24726            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24727        })
24728        .await
24729        .unwrap();
24730
24731    let (editor, cx) = cx.add_window_view(|window, cx| {
24732        Editor::new(
24733            EditorMode::full(),
24734            MultiBuffer::build_from_buffer(buffer, cx),
24735            Some(project.clone()),
24736            window,
24737            cx,
24738        )
24739    });
24740
24741    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24742    let abs_path = project.read_with(cx, |project, cx| {
24743        project
24744            .absolute_path(&project_path, cx)
24745            .map(Arc::from)
24746            .unwrap()
24747    });
24748
24749    // assert we can add breakpoint on the first line
24750    editor.update_in(cx, |editor, window, cx| {
24751        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24752        editor.move_to_end(&MoveToEnd, window, cx);
24753        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24754        editor.move_up(&MoveUp, window, cx);
24755        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24756    });
24757
24758    let breakpoints = editor.update(cx, |editor, cx| {
24759        editor
24760            .breakpoint_store()
24761            .as_ref()
24762            .unwrap()
24763            .read(cx)
24764            .all_source_breakpoints(cx)
24765    });
24766
24767    assert_eq!(1, breakpoints.len());
24768    assert_breakpoint(
24769        &breakpoints,
24770        &abs_path,
24771        vec![
24772            (0, Breakpoint::new_standard()),
24773            (2, Breakpoint::new_standard()),
24774            (3, Breakpoint::new_standard()),
24775        ],
24776    );
24777
24778    editor.update_in(cx, |editor, window, cx| {
24779        editor.move_to_beginning(&MoveToBeginning, window, cx);
24780        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24781        editor.move_to_end(&MoveToEnd, window, cx);
24782        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24783        // Disabling a breakpoint that doesn't exist should do nothing
24784        editor.move_up(&MoveUp, window, cx);
24785        editor.move_up(&MoveUp, window, cx);
24786        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24787    });
24788
24789    let breakpoints = editor.update(cx, |editor, cx| {
24790        editor
24791            .breakpoint_store()
24792            .as_ref()
24793            .unwrap()
24794            .read(cx)
24795            .all_source_breakpoints(cx)
24796    });
24797
24798    let disable_breakpoint = {
24799        let mut bp = Breakpoint::new_standard();
24800        bp.state = BreakpointState::Disabled;
24801        bp
24802    };
24803
24804    assert_eq!(1, breakpoints.len());
24805    assert_breakpoint(
24806        &breakpoints,
24807        &abs_path,
24808        vec![
24809            (0, disable_breakpoint.clone()),
24810            (2, Breakpoint::new_standard()),
24811            (3, disable_breakpoint.clone()),
24812        ],
24813    );
24814
24815    editor.update_in(cx, |editor, window, cx| {
24816        editor.move_to_beginning(&MoveToBeginning, window, cx);
24817        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24818        editor.move_to_end(&MoveToEnd, window, cx);
24819        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24820        editor.move_up(&MoveUp, window, cx);
24821        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24822    });
24823
24824    let breakpoints = editor.update(cx, |editor, cx| {
24825        editor
24826            .breakpoint_store()
24827            .as_ref()
24828            .unwrap()
24829            .read(cx)
24830            .all_source_breakpoints(cx)
24831    });
24832
24833    assert_eq!(1, breakpoints.len());
24834    assert_breakpoint(
24835        &breakpoints,
24836        &abs_path,
24837        vec![
24838            (0, Breakpoint::new_standard()),
24839            (2, disable_breakpoint),
24840            (3, Breakpoint::new_standard()),
24841        ],
24842    );
24843}
24844
24845#[gpui::test]
24846async fn test_breakpoint_phantom_indicator_collision_on_toggle(cx: &mut TestAppContext) {
24847    init_test(cx, |_| {});
24848
24849    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24850    let fs = FakeFs::new(cx.executor());
24851    fs.insert_tree(
24852        path!("/a"),
24853        json!({
24854            "main.rs": sample_text,
24855        }),
24856    )
24857    .await;
24858    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24859    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24860    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24861    let worktree_id = workspace
24862        .update(cx, |workspace, _window, cx| {
24863            workspace.project().update(cx, |project, cx| {
24864                project.worktrees(cx).next().unwrap().read(cx).id()
24865            })
24866        })
24867        .unwrap();
24868
24869    let buffer = project
24870        .update(cx, |project, cx| {
24871            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24872        })
24873        .await
24874        .unwrap();
24875
24876    let (editor, cx) = cx.add_window_view(|window, cx| {
24877        Editor::new(
24878            EditorMode::full(),
24879            MultiBuffer::build_from_buffer(buffer, cx),
24880            Some(project.clone()),
24881            window,
24882            cx,
24883        )
24884    });
24885
24886    // Simulate hovering over row 0 with no existing breakpoint.
24887    editor.update(cx, |editor, _cx| {
24888        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
24889            display_row: DisplayRow(0),
24890            is_active: true,
24891            collides_with_existing_breakpoint: false,
24892        });
24893    });
24894
24895    // Toggle breakpoint on the same row (row 0) — collision should flip to true.
24896    editor.update_in(cx, |editor, window, cx| {
24897        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24898    });
24899    editor.update(cx, |editor, _cx| {
24900        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
24901        assert!(
24902            indicator.collides_with_existing_breakpoint,
24903            "Adding a breakpoint on the hovered row should set collision to true"
24904        );
24905    });
24906
24907    // Toggle again on the same row — breakpoint is removed, collision should flip back to false.
24908    editor.update_in(cx, |editor, window, cx| {
24909        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24910    });
24911    editor.update(cx, |editor, _cx| {
24912        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
24913        assert!(
24914            !indicator.collides_with_existing_breakpoint,
24915            "Removing a breakpoint on the hovered row should set collision to false"
24916        );
24917    });
24918
24919    // Now move cursor to row 2 while phantom indicator stays on row 0.
24920    editor.update_in(cx, |editor, window, cx| {
24921        editor.move_down(&MoveDown, window, cx);
24922        editor.move_down(&MoveDown, window, cx);
24923    });
24924
24925    // Ensure phantom indicator is still on row 0, not colliding.
24926    editor.update(cx, |editor, _cx| {
24927        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
24928            display_row: DisplayRow(0),
24929            is_active: true,
24930            collides_with_existing_breakpoint: false,
24931        });
24932    });
24933
24934    // Toggle breakpoint on row 2 (cursor row) — phantom on row 0 should NOT be affected.
24935    editor.update_in(cx, |editor, window, cx| {
24936        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24937    });
24938    editor.update(cx, |editor, _cx| {
24939        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
24940        assert!(
24941            !indicator.collides_with_existing_breakpoint,
24942            "Toggling a breakpoint on a different row should not affect the phantom indicator"
24943        );
24944    });
24945}
24946
24947#[gpui::test]
24948async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24949    init_test(cx, |_| {});
24950    let capabilities = lsp::ServerCapabilities {
24951        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24952            prepare_provider: Some(true),
24953            work_done_progress_options: Default::default(),
24954        })),
24955        ..Default::default()
24956    };
24957    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24958
24959    cx.set_state(indoc! {"
24960        struct Fˇoo {}
24961    "});
24962
24963    cx.update_editor(|editor, _, cx| {
24964        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24965        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24966        editor.highlight_background(
24967            HighlightKey::DocumentHighlightRead,
24968            &[highlight_range],
24969            |_, theme| theme.colors().editor_document_highlight_read_background,
24970            cx,
24971        );
24972    });
24973
24974    let mut prepare_rename_handler = cx
24975        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24976            move |_, _, _| async move {
24977                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24978                    start: lsp::Position {
24979                        line: 0,
24980                        character: 7,
24981                    },
24982                    end: lsp::Position {
24983                        line: 0,
24984                        character: 10,
24985                    },
24986                })))
24987            },
24988        );
24989    let prepare_rename_task = cx
24990        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24991        .expect("Prepare rename was not started");
24992    prepare_rename_handler.next().await.unwrap();
24993    prepare_rename_task.await.expect("Prepare rename failed");
24994
24995    let mut rename_handler =
24996        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24997            let edit = lsp::TextEdit {
24998                range: lsp::Range {
24999                    start: lsp::Position {
25000                        line: 0,
25001                        character: 7,
25002                    },
25003                    end: lsp::Position {
25004                        line: 0,
25005                        character: 10,
25006                    },
25007                },
25008                new_text: "FooRenamed".to_string(),
25009            };
25010            Ok(Some(lsp::WorkspaceEdit::new(
25011                // Specify the same edit twice
25012                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
25013            )))
25014        });
25015    let rename_task = cx
25016        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
25017        .expect("Confirm rename was not started");
25018    rename_handler.next().await.unwrap();
25019    rename_task.await.expect("Confirm rename failed");
25020    cx.run_until_parked();
25021
25022    // Despite two edits, only one is actually applied as those are identical
25023    cx.assert_editor_state(indoc! {"
25024        struct FooRenamedˇ {}
25025    "});
25026}
25027
25028#[gpui::test]
25029async fn test_rename_without_prepare(cx: &mut TestAppContext) {
25030    init_test(cx, |_| {});
25031    // These capabilities indicate that the server does not support prepare rename.
25032    let capabilities = lsp::ServerCapabilities {
25033        rename_provider: Some(lsp::OneOf::Left(true)),
25034        ..Default::default()
25035    };
25036    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
25037
25038    cx.set_state(indoc! {"
25039        struct Fˇoo {}
25040    "});
25041
25042    cx.update_editor(|editor, _window, cx| {
25043        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
25044        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
25045        editor.highlight_background(
25046            HighlightKey::DocumentHighlightRead,
25047            &[highlight_range],
25048            |_, theme| theme.colors().editor_document_highlight_read_background,
25049            cx,
25050        );
25051    });
25052
25053    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
25054        .expect("Prepare rename was not started")
25055        .await
25056        .expect("Prepare rename failed");
25057
25058    let mut rename_handler =
25059        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
25060            let edit = lsp::TextEdit {
25061                range: lsp::Range {
25062                    start: lsp::Position {
25063                        line: 0,
25064                        character: 7,
25065                    },
25066                    end: lsp::Position {
25067                        line: 0,
25068                        character: 10,
25069                    },
25070                },
25071                new_text: "FooRenamed".to_string(),
25072            };
25073            Ok(Some(lsp::WorkspaceEdit::new(
25074                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
25075            )))
25076        });
25077    let rename_task = cx
25078        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
25079        .expect("Confirm rename was not started");
25080    rename_handler.next().await.unwrap();
25081    rename_task.await.expect("Confirm rename failed");
25082    cx.run_until_parked();
25083
25084    // Correct range is renamed, as `surrounding_word` is used to find it.
25085    cx.assert_editor_state(indoc! {"
25086        struct FooRenamedˇ {}
25087    "});
25088}
25089
25090#[gpui::test]
25091async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
25092    init_test(cx, |_| {});
25093    let mut cx = EditorTestContext::new(cx).await;
25094
25095    let language = Arc::new(
25096        Language::new(
25097            LanguageConfig::default(),
25098            Some(tree_sitter_html::LANGUAGE.into()),
25099        )
25100        .with_brackets_query(
25101            r#"
25102            ("<" @open "/>" @close)
25103            ("</" @open ">" @close)
25104            ("<" @open ">" @close)
25105            ("\"" @open "\"" @close)
25106            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
25107        "#,
25108        )
25109        .unwrap(),
25110    );
25111    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25112
25113    cx.set_state(indoc! {"
25114        <span>ˇ</span>
25115    "});
25116    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
25117    cx.assert_editor_state(indoc! {"
25118        <span>
25119        ˇ
25120        </span>
25121    "});
25122
25123    cx.set_state(indoc! {"
25124        <span><span></span>ˇ</span>
25125    "});
25126    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
25127    cx.assert_editor_state(indoc! {"
25128        <span><span></span>
25129        ˇ</span>
25130    "});
25131
25132    cx.set_state(indoc! {"
25133        <span>ˇ
25134        </span>
25135    "});
25136    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
25137    cx.assert_editor_state(indoc! {"
25138        <span>
25139        ˇ
25140        </span>
25141    "});
25142}
25143
25144#[gpui::test(iterations = 10)]
25145async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
25146    init_test(cx, |_| {});
25147
25148    let fs = FakeFs::new(cx.executor());
25149    fs.insert_tree(
25150        path!("/dir"),
25151        json!({
25152            "a.ts": "a",
25153        }),
25154    )
25155    .await;
25156
25157    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
25158    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25159    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25160
25161    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25162    language_registry.add(Arc::new(Language::new(
25163        LanguageConfig {
25164            name: "TypeScript".into(),
25165            matcher: LanguageMatcher {
25166                path_suffixes: vec!["ts".to_string()],
25167                ..Default::default()
25168            },
25169            ..Default::default()
25170        },
25171        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
25172    )));
25173    let mut fake_language_servers = language_registry.register_fake_lsp(
25174        "TypeScript",
25175        FakeLspAdapter {
25176            capabilities: lsp::ServerCapabilities {
25177                code_lens_provider: Some(lsp::CodeLensOptions {
25178                    resolve_provider: Some(true),
25179                }),
25180                execute_command_provider: Some(lsp::ExecuteCommandOptions {
25181                    commands: vec!["_the/command".to_string()],
25182                    ..lsp::ExecuteCommandOptions::default()
25183                }),
25184                ..lsp::ServerCapabilities::default()
25185            },
25186            ..FakeLspAdapter::default()
25187        },
25188    );
25189
25190    let editor = workspace
25191        .update(cx, |workspace, window, cx| {
25192            workspace.open_abs_path(
25193                PathBuf::from(path!("/dir/a.ts")),
25194                OpenOptions::default(),
25195                window,
25196                cx,
25197            )
25198        })
25199        .unwrap()
25200        .await
25201        .unwrap()
25202        .downcast::<Editor>()
25203        .unwrap();
25204    cx.executor().run_until_parked();
25205
25206    let fake_server = fake_language_servers.next().await.unwrap();
25207
25208    let buffer = editor.update(cx, |editor, cx| {
25209        editor
25210            .buffer()
25211            .read(cx)
25212            .as_singleton()
25213            .expect("have opened a single file by path")
25214    });
25215
25216    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
25217    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
25218    drop(buffer_snapshot);
25219    let actions = cx
25220        .update_window(*workspace, |_, window, cx| {
25221            project.code_actions(&buffer, anchor..anchor, window, cx)
25222        })
25223        .unwrap();
25224
25225    fake_server
25226        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
25227            Ok(Some(vec![
25228                lsp::CodeLens {
25229                    range: lsp::Range::default(),
25230                    command: Some(lsp::Command {
25231                        title: "Code lens command".to_owned(),
25232                        command: "_the/command".to_owned(),
25233                        arguments: None,
25234                    }),
25235                    data: None,
25236                },
25237                lsp::CodeLens {
25238                    range: lsp::Range::default(),
25239                    command: Some(lsp::Command {
25240                        title: "Command not in capabilities".to_owned(),
25241                        command: "not in capabilities".to_owned(),
25242                        arguments: None,
25243                    }),
25244                    data: None,
25245                },
25246                lsp::CodeLens {
25247                    range: lsp::Range {
25248                        start: lsp::Position {
25249                            line: 1,
25250                            character: 1,
25251                        },
25252                        end: lsp::Position {
25253                            line: 1,
25254                            character: 1,
25255                        },
25256                    },
25257                    command: Some(lsp::Command {
25258                        title: "Command not in range".to_owned(),
25259                        command: "_the/command".to_owned(),
25260                        arguments: None,
25261                    }),
25262                    data: None,
25263                },
25264            ]))
25265        })
25266        .next()
25267        .await;
25268
25269    let actions = actions.await.unwrap();
25270    assert_eq!(
25271        actions.len(),
25272        1,
25273        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
25274    );
25275    let action = actions[0].clone();
25276    let apply = project.update(cx, |project, cx| {
25277        project.apply_code_action(buffer.clone(), action, true, cx)
25278    });
25279
25280    // Resolving the code action does not populate its edits. In absence of
25281    // edits, we must execute the given command.
25282    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
25283        |mut lens, _| async move {
25284            let lens_command = lens.command.as_mut().expect("should have a command");
25285            assert_eq!(lens_command.title, "Code lens command");
25286            lens_command.arguments = Some(vec![json!("the-argument")]);
25287            Ok(lens)
25288        },
25289    );
25290
25291    // While executing the command, the language server sends the editor
25292    // a `workspaceEdit` request.
25293    fake_server
25294        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
25295            let fake = fake_server.clone();
25296            move |params, _| {
25297                assert_eq!(params.command, "_the/command");
25298                let fake = fake.clone();
25299                async move {
25300                    fake.server
25301                        .request::<lsp::request::ApplyWorkspaceEdit>(
25302                            lsp::ApplyWorkspaceEditParams {
25303                                label: None,
25304                                edit: lsp::WorkspaceEdit {
25305                                    changes: Some(
25306                                        [(
25307                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
25308                                            vec![lsp::TextEdit {
25309                                                range: lsp::Range::new(
25310                                                    lsp::Position::new(0, 0),
25311                                                    lsp::Position::new(0, 0),
25312                                                ),
25313                                                new_text: "X".into(),
25314                                            }],
25315                                        )]
25316                                        .into_iter()
25317                                        .collect(),
25318                                    ),
25319                                    ..lsp::WorkspaceEdit::default()
25320                                },
25321                            },
25322                            DEFAULT_LSP_REQUEST_TIMEOUT,
25323                        )
25324                        .await
25325                        .into_response()
25326                        .unwrap();
25327                    Ok(Some(json!(null)))
25328                }
25329            }
25330        })
25331        .next()
25332        .await;
25333
25334    // Applying the code lens command returns a project transaction containing the edits
25335    // sent by the language server in its `workspaceEdit` request.
25336    let transaction = apply.await.unwrap();
25337    assert!(transaction.0.contains_key(&buffer));
25338    buffer.update(cx, |buffer, cx| {
25339        assert_eq!(buffer.text(), "Xa");
25340        buffer.undo(cx);
25341        assert_eq!(buffer.text(), "a");
25342    });
25343
25344    let actions_after_edits = cx
25345        .update_window(*workspace, |_, window, cx| {
25346            project.code_actions(&buffer, anchor..anchor, window, cx)
25347        })
25348        .unwrap()
25349        .await
25350        .unwrap();
25351    assert_eq!(
25352        actions, actions_after_edits,
25353        "For the same selection, same code lens actions should be returned"
25354    );
25355
25356    let _responses =
25357        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
25358            panic!("No more code lens requests are expected");
25359        });
25360    editor.update_in(cx, |editor, window, cx| {
25361        editor.select_all(&SelectAll, window, cx);
25362    });
25363    cx.executor().run_until_parked();
25364    let new_actions = cx
25365        .update_window(*workspace, |_, window, cx| {
25366            project.code_actions(&buffer, anchor..anchor, window, cx)
25367        })
25368        .unwrap()
25369        .await
25370        .unwrap();
25371    assert_eq!(
25372        actions, new_actions,
25373        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
25374    );
25375}
25376
25377#[gpui::test]
25378async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
25379    init_test(cx, |_| {});
25380
25381    let fs = FakeFs::new(cx.executor());
25382    let main_text = r#"fn main() {
25383println!("1");
25384println!("2");
25385println!("3");
25386println!("4");
25387println!("5");
25388}"#;
25389    let lib_text = "mod foo {}";
25390    fs.insert_tree(
25391        path!("/a"),
25392        json!({
25393            "lib.rs": lib_text,
25394            "main.rs": main_text,
25395        }),
25396    )
25397    .await;
25398
25399    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25400    let (workspace, cx) =
25401        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25402    let worktree_id = workspace.update(cx, |workspace, cx| {
25403        workspace.project().update(cx, |project, cx| {
25404            project.worktrees(cx).next().unwrap().read(cx).id()
25405        })
25406    });
25407
25408    let expected_ranges = vec![
25409        Point::new(0, 0)..Point::new(0, 0),
25410        Point::new(1, 0)..Point::new(1, 1),
25411        Point::new(2, 0)..Point::new(2, 2),
25412        Point::new(3, 0)..Point::new(3, 3),
25413    ];
25414
25415    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25416    let editor_1 = workspace
25417        .update_in(cx, |workspace, window, cx| {
25418            workspace.open_path(
25419                (worktree_id, rel_path("main.rs")),
25420                Some(pane_1.downgrade()),
25421                true,
25422                window,
25423                cx,
25424            )
25425        })
25426        .unwrap()
25427        .await
25428        .downcast::<Editor>()
25429        .unwrap();
25430    pane_1.update(cx, |pane, cx| {
25431        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25432        open_editor.update(cx, |editor, cx| {
25433            assert_eq!(
25434                editor.display_text(cx),
25435                main_text,
25436                "Original main.rs text on initial open",
25437            );
25438            assert_eq!(
25439                editor
25440                    .selections
25441                    .all::<Point>(&editor.display_snapshot(cx))
25442                    .into_iter()
25443                    .map(|s| s.range())
25444                    .collect::<Vec<_>>(),
25445                vec![Point::zero()..Point::zero()],
25446                "Default selections on initial open",
25447            );
25448        })
25449    });
25450    editor_1.update_in(cx, |editor, window, cx| {
25451        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25452            s.select_ranges(expected_ranges.clone());
25453        });
25454    });
25455
25456    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
25457        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
25458    });
25459    let editor_2 = workspace
25460        .update_in(cx, |workspace, window, cx| {
25461            workspace.open_path(
25462                (worktree_id, rel_path("main.rs")),
25463                Some(pane_2.downgrade()),
25464                true,
25465                window,
25466                cx,
25467            )
25468        })
25469        .unwrap()
25470        .await
25471        .downcast::<Editor>()
25472        .unwrap();
25473    pane_2.update(cx, |pane, cx| {
25474        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25475        open_editor.update(cx, |editor, cx| {
25476            assert_eq!(
25477                editor.display_text(cx),
25478                main_text,
25479                "Original main.rs text on initial open in another panel",
25480            );
25481            assert_eq!(
25482                editor
25483                    .selections
25484                    .all::<Point>(&editor.display_snapshot(cx))
25485                    .into_iter()
25486                    .map(|s| s.range())
25487                    .collect::<Vec<_>>(),
25488                vec![Point::zero()..Point::zero()],
25489                "Default selections on initial open in another panel",
25490            );
25491        })
25492    });
25493
25494    editor_2.update_in(cx, |editor, window, cx| {
25495        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
25496    });
25497
25498    let _other_editor_1 = workspace
25499        .update_in(cx, |workspace, window, cx| {
25500            workspace.open_path(
25501                (worktree_id, rel_path("lib.rs")),
25502                Some(pane_1.downgrade()),
25503                true,
25504                window,
25505                cx,
25506            )
25507        })
25508        .unwrap()
25509        .await
25510        .downcast::<Editor>()
25511        .unwrap();
25512    pane_1
25513        .update_in(cx, |pane, window, cx| {
25514            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25515        })
25516        .await
25517        .unwrap();
25518    drop(editor_1);
25519    pane_1.update(cx, |pane, cx| {
25520        pane.active_item()
25521            .unwrap()
25522            .downcast::<Editor>()
25523            .unwrap()
25524            .update(cx, |editor, cx| {
25525                assert_eq!(
25526                    editor.display_text(cx),
25527                    lib_text,
25528                    "Other file should be open and active",
25529                );
25530            });
25531        assert_eq!(pane.items().count(), 1, "No other editors should be open");
25532    });
25533
25534    let _other_editor_2 = workspace
25535        .update_in(cx, |workspace, window, cx| {
25536            workspace.open_path(
25537                (worktree_id, rel_path("lib.rs")),
25538                Some(pane_2.downgrade()),
25539                true,
25540                window,
25541                cx,
25542            )
25543        })
25544        .unwrap()
25545        .await
25546        .downcast::<Editor>()
25547        .unwrap();
25548    pane_2
25549        .update_in(cx, |pane, window, cx| {
25550            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25551        })
25552        .await
25553        .unwrap();
25554    drop(editor_2);
25555    pane_2.update(cx, |pane, cx| {
25556        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25557        open_editor.update(cx, |editor, cx| {
25558            assert_eq!(
25559                editor.display_text(cx),
25560                lib_text,
25561                "Other file should be open and active in another panel too",
25562            );
25563        });
25564        assert_eq!(
25565            pane.items().count(),
25566            1,
25567            "No other editors should be open in another pane",
25568        );
25569    });
25570
25571    let _editor_1_reopened = workspace
25572        .update_in(cx, |workspace, window, cx| {
25573            workspace.open_path(
25574                (worktree_id, rel_path("main.rs")),
25575                Some(pane_1.downgrade()),
25576                true,
25577                window,
25578                cx,
25579            )
25580        })
25581        .unwrap()
25582        .await
25583        .downcast::<Editor>()
25584        .unwrap();
25585    let _editor_2_reopened = workspace
25586        .update_in(cx, |workspace, window, cx| {
25587            workspace.open_path(
25588                (worktree_id, rel_path("main.rs")),
25589                Some(pane_2.downgrade()),
25590                true,
25591                window,
25592                cx,
25593            )
25594        })
25595        .unwrap()
25596        .await
25597        .downcast::<Editor>()
25598        .unwrap();
25599    pane_1.update(cx, |pane, cx| {
25600        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25601        open_editor.update(cx, |editor, cx| {
25602            assert_eq!(
25603                editor.display_text(cx),
25604                main_text,
25605                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
25606            );
25607            assert_eq!(
25608                editor
25609                    .selections
25610                    .all::<Point>(&editor.display_snapshot(cx))
25611                    .into_iter()
25612                    .map(|s| s.range())
25613                    .collect::<Vec<_>>(),
25614                expected_ranges,
25615                "Previous editor in the 1st panel had selections and should get them restored on reopen",
25616            );
25617        })
25618    });
25619    pane_2.update(cx, |pane, cx| {
25620        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25621        open_editor.update(cx, |editor, cx| {
25622            assert_eq!(
25623                editor.display_text(cx),
25624                r#"fn main() {
25625⋯rintln!("1");
25626⋯intln!("2");
25627⋯ntln!("3");
25628println!("4");
25629println!("5");
25630}"#,
25631                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
25632            );
25633            assert_eq!(
25634                editor
25635                    .selections
25636                    .all::<Point>(&editor.display_snapshot(cx))
25637                    .into_iter()
25638                    .map(|s| s.range())
25639                    .collect::<Vec<_>>(),
25640                vec![Point::zero()..Point::zero()],
25641                "Previous editor in the 2nd pane had no selections changed hence should restore none",
25642            );
25643        })
25644    });
25645}
25646
25647#[gpui::test]
25648async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
25649    init_test(cx, |_| {});
25650
25651    let fs = FakeFs::new(cx.executor());
25652    let main_text = r#"fn main() {
25653println!("1");
25654println!("2");
25655println!("3");
25656println!("4");
25657println!("5");
25658}"#;
25659    let lib_text = "mod foo {}";
25660    fs.insert_tree(
25661        path!("/a"),
25662        json!({
25663            "lib.rs": lib_text,
25664            "main.rs": main_text,
25665        }),
25666    )
25667    .await;
25668
25669    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25670    let (workspace, cx) =
25671        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25672    let worktree_id = workspace.update(cx, |workspace, cx| {
25673        workspace.project().update(cx, |project, cx| {
25674            project.worktrees(cx).next().unwrap().read(cx).id()
25675        })
25676    });
25677
25678    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25679    let editor = workspace
25680        .update_in(cx, |workspace, window, cx| {
25681            workspace.open_path(
25682                (worktree_id, rel_path("main.rs")),
25683                Some(pane.downgrade()),
25684                true,
25685                window,
25686                cx,
25687            )
25688        })
25689        .unwrap()
25690        .await
25691        .downcast::<Editor>()
25692        .unwrap();
25693    pane.update(cx, |pane, cx| {
25694        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25695        open_editor.update(cx, |editor, cx| {
25696            assert_eq!(
25697                editor.display_text(cx),
25698                main_text,
25699                "Original main.rs text on initial open",
25700            );
25701        })
25702    });
25703    editor.update_in(cx, |editor, window, cx| {
25704        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
25705    });
25706
25707    cx.update_global(|store: &mut SettingsStore, cx| {
25708        store.update_user_settings(cx, |s| {
25709            s.workspace.restore_on_file_reopen = Some(false);
25710        });
25711    });
25712    editor.update_in(cx, |editor, window, cx| {
25713        editor.fold_ranges(
25714            vec![
25715                Point::new(1, 0)..Point::new(1, 1),
25716                Point::new(2, 0)..Point::new(2, 2),
25717                Point::new(3, 0)..Point::new(3, 3),
25718            ],
25719            false,
25720            window,
25721            cx,
25722        );
25723    });
25724    pane.update_in(cx, |pane, window, cx| {
25725        pane.close_all_items(&CloseAllItems::default(), window, cx)
25726    })
25727    .await
25728    .unwrap();
25729    pane.update(cx, |pane, _| {
25730        assert!(pane.active_item().is_none());
25731    });
25732    cx.update_global(|store: &mut SettingsStore, cx| {
25733        store.update_user_settings(cx, |s| {
25734            s.workspace.restore_on_file_reopen = Some(true);
25735        });
25736    });
25737
25738    let _editor_reopened = workspace
25739        .update_in(cx, |workspace, window, cx| {
25740            workspace.open_path(
25741                (worktree_id, rel_path("main.rs")),
25742                Some(pane.downgrade()),
25743                true,
25744                window,
25745                cx,
25746            )
25747        })
25748        .unwrap()
25749        .await
25750        .downcast::<Editor>()
25751        .unwrap();
25752    pane.update(cx, |pane, cx| {
25753        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25754        open_editor.update(cx, |editor, cx| {
25755            assert_eq!(
25756                editor.display_text(cx),
25757                main_text,
25758                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
25759            );
25760        })
25761    });
25762}
25763
25764#[gpui::test]
25765async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
25766    struct EmptyModalView {
25767        focus_handle: gpui::FocusHandle,
25768    }
25769    impl EventEmitter<DismissEvent> for EmptyModalView {}
25770    impl Render for EmptyModalView {
25771        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
25772            div()
25773        }
25774    }
25775    impl Focusable for EmptyModalView {
25776        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
25777            self.focus_handle.clone()
25778        }
25779    }
25780    impl workspace::ModalView for EmptyModalView {}
25781    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
25782        EmptyModalView {
25783            focus_handle: cx.focus_handle(),
25784        }
25785    }
25786
25787    init_test(cx, |_| {});
25788
25789    let fs = FakeFs::new(cx.executor());
25790    let project = Project::test(fs, [], cx).await;
25791    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25792    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
25793    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25794    let editor = cx.new_window_entity(|window, cx| {
25795        Editor::new(
25796            EditorMode::full(),
25797            buffer,
25798            Some(project.clone()),
25799            window,
25800            cx,
25801        )
25802    });
25803    workspace
25804        .update(cx, |workspace, window, cx| {
25805            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
25806        })
25807        .unwrap();
25808    editor.update_in(cx, |editor, window, cx| {
25809        editor.open_context_menu(&OpenContextMenu, window, cx);
25810        assert!(editor.mouse_context_menu.is_some());
25811    });
25812    workspace
25813        .update(cx, |workspace, window, cx| {
25814            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
25815        })
25816        .unwrap();
25817    cx.read(|cx| {
25818        assert!(editor.read(cx).mouse_context_menu.is_none());
25819    });
25820}
25821
25822fn set_linked_edit_ranges(
25823    opening: (Point, Point),
25824    closing: (Point, Point),
25825    editor: &mut Editor,
25826    cx: &mut Context<Editor>,
25827) {
25828    let Some((buffer, _)) = editor
25829        .buffer
25830        .read(cx)
25831        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
25832    else {
25833        panic!("Failed to get buffer for selection position");
25834    };
25835    let buffer = buffer.read(cx);
25836    let buffer_id = buffer.remote_id();
25837    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
25838    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
25839    let mut linked_ranges = HashMap::default();
25840    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
25841    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
25842}
25843
25844#[gpui::test]
25845async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
25846    init_test(cx, |_| {});
25847
25848    let fs = FakeFs::new(cx.executor());
25849    fs.insert_file(path!("/file.html"), Default::default())
25850        .await;
25851
25852    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
25853
25854    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25855    let html_language = Arc::new(Language::new(
25856        LanguageConfig {
25857            name: "HTML".into(),
25858            matcher: LanguageMatcher {
25859                path_suffixes: vec!["html".to_string()],
25860                ..LanguageMatcher::default()
25861            },
25862            brackets: BracketPairConfig {
25863                pairs: vec![BracketPair {
25864                    start: "<".into(),
25865                    end: ">".into(),
25866                    close: true,
25867                    ..Default::default()
25868                }],
25869                ..Default::default()
25870            },
25871            ..Default::default()
25872        },
25873        Some(tree_sitter_html::LANGUAGE.into()),
25874    ));
25875    language_registry.add(html_language);
25876    let mut fake_servers = language_registry.register_fake_lsp(
25877        "HTML",
25878        FakeLspAdapter {
25879            capabilities: lsp::ServerCapabilities {
25880                completion_provider: Some(lsp::CompletionOptions {
25881                    resolve_provider: Some(true),
25882                    ..Default::default()
25883                }),
25884                ..Default::default()
25885            },
25886            ..Default::default()
25887        },
25888    );
25889
25890    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25891    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25892
25893    let worktree_id = workspace
25894        .update(cx, |workspace, _window, cx| {
25895            workspace.project().update(cx, |project, cx| {
25896                project.worktrees(cx).next().unwrap().read(cx).id()
25897            })
25898        })
25899        .unwrap();
25900    project
25901        .update(cx, |project, cx| {
25902            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25903        })
25904        .await
25905        .unwrap();
25906    let editor = workspace
25907        .update(cx, |workspace, window, cx| {
25908            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25909        })
25910        .unwrap()
25911        .await
25912        .unwrap()
25913        .downcast::<Editor>()
25914        .unwrap();
25915
25916    let fake_server = fake_servers.next().await.unwrap();
25917    cx.run_until_parked();
25918    editor.update_in(cx, |editor, window, cx| {
25919        editor.set_text("<ad></ad>", window, cx);
25920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25921            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25922        });
25923        set_linked_edit_ranges(
25924            (Point::new(0, 1), Point::new(0, 3)),
25925            (Point::new(0, 6), Point::new(0, 8)),
25926            editor,
25927            cx,
25928        );
25929    });
25930    let mut completion_handle =
25931        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25932            Ok(Some(lsp::CompletionResponse::Array(vec![
25933                lsp::CompletionItem {
25934                    label: "head".to_string(),
25935                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25936                        lsp::InsertReplaceEdit {
25937                            new_text: "head".to_string(),
25938                            insert: lsp::Range::new(
25939                                lsp::Position::new(0, 1),
25940                                lsp::Position::new(0, 3),
25941                            ),
25942                            replace: lsp::Range::new(
25943                                lsp::Position::new(0, 1),
25944                                lsp::Position::new(0, 3),
25945                            ),
25946                        },
25947                    )),
25948                    ..Default::default()
25949                },
25950            ])))
25951        });
25952    editor.update_in(cx, |editor, window, cx| {
25953        editor.show_completions(&ShowCompletions, window, cx);
25954    });
25955    cx.run_until_parked();
25956    completion_handle.next().await.unwrap();
25957    editor.update(cx, |editor, _| {
25958        assert!(
25959            editor.context_menu_visible(),
25960            "Completion menu should be visible"
25961        );
25962    });
25963    editor.update_in(cx, |editor, window, cx| {
25964        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25965    });
25966    cx.executor().run_until_parked();
25967    editor.update(cx, |editor, cx| {
25968        assert_eq!(editor.text(cx), "<head></head>");
25969    });
25970}
25971
25972#[gpui::test]
25973async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25974    init_test(cx, |_| {});
25975
25976    let mut cx = EditorTestContext::new(cx).await;
25977    let language = Arc::new(Language::new(
25978        LanguageConfig {
25979            name: "TSX".into(),
25980            matcher: LanguageMatcher {
25981                path_suffixes: vec!["tsx".to_string()],
25982                ..LanguageMatcher::default()
25983            },
25984            brackets: BracketPairConfig {
25985                pairs: vec![BracketPair {
25986                    start: "<".into(),
25987                    end: ">".into(),
25988                    close: true,
25989                    ..Default::default()
25990                }],
25991                ..Default::default()
25992            },
25993            linked_edit_characters: HashSet::from_iter(['.']),
25994            ..Default::default()
25995        },
25996        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25997    ));
25998    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25999
26000    // Test typing > does not extend linked pair
26001    cx.set_state("<divˇ<div></div>");
26002    cx.update_editor(|editor, _, cx| {
26003        set_linked_edit_ranges(
26004            (Point::new(0, 1), Point::new(0, 4)),
26005            (Point::new(0, 11), Point::new(0, 14)),
26006            editor,
26007            cx,
26008        );
26009    });
26010    cx.update_editor(|editor, window, cx| {
26011        editor.handle_input(">", window, cx);
26012    });
26013    cx.assert_editor_state("<div>ˇ<div></div>");
26014
26015    // Test typing . do extend linked pair
26016    cx.set_state("<Animatedˇ></Animated>");
26017    cx.update_editor(|editor, _, cx| {
26018        set_linked_edit_ranges(
26019            (Point::new(0, 1), Point::new(0, 9)),
26020            (Point::new(0, 12), Point::new(0, 20)),
26021            editor,
26022            cx,
26023        );
26024    });
26025    cx.update_editor(|editor, window, cx| {
26026        editor.handle_input(".", window, cx);
26027    });
26028    cx.assert_editor_state("<Animated.ˇ></Animated.>");
26029    cx.update_editor(|editor, _, cx| {
26030        set_linked_edit_ranges(
26031            (Point::new(0, 1), Point::new(0, 10)),
26032            (Point::new(0, 13), Point::new(0, 21)),
26033            editor,
26034            cx,
26035        );
26036    });
26037    cx.update_editor(|editor, window, cx| {
26038        editor.handle_input("V", window, cx);
26039    });
26040    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
26041}
26042
26043#[gpui::test]
26044async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
26045    init_test(cx, |_| {});
26046
26047    let fs = FakeFs::new(cx.executor());
26048    fs.insert_tree(
26049        path!("/root"),
26050        json!({
26051            "a": {
26052                "main.rs": "fn main() {}",
26053            },
26054            "foo": {
26055                "bar": {
26056                    "external_file.rs": "pub mod external {}",
26057                }
26058            }
26059        }),
26060    )
26061    .await;
26062
26063    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
26064    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26065    language_registry.add(rust_lang());
26066    let _fake_servers = language_registry.register_fake_lsp(
26067        "Rust",
26068        FakeLspAdapter {
26069            ..FakeLspAdapter::default()
26070        },
26071    );
26072    let (workspace, cx) =
26073        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26074    let worktree_id = workspace.update(cx, |workspace, cx| {
26075        workspace.project().update(cx, |project, cx| {
26076            project.worktrees(cx).next().unwrap().read(cx).id()
26077        })
26078    });
26079
26080    let assert_language_servers_count =
26081        |expected: usize, context: &str, cx: &mut VisualTestContext| {
26082            project.update(cx, |project, cx| {
26083                let current = project
26084                    .lsp_store()
26085                    .read(cx)
26086                    .as_local()
26087                    .unwrap()
26088                    .language_servers
26089                    .len();
26090                assert_eq!(expected, current, "{context}");
26091            });
26092        };
26093
26094    assert_language_servers_count(
26095        0,
26096        "No servers should be running before any file is open",
26097        cx,
26098    );
26099    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
26100    let main_editor = workspace
26101        .update_in(cx, |workspace, window, cx| {
26102            workspace.open_path(
26103                (worktree_id, rel_path("main.rs")),
26104                Some(pane.downgrade()),
26105                true,
26106                window,
26107                cx,
26108            )
26109        })
26110        .unwrap()
26111        .await
26112        .downcast::<Editor>()
26113        .unwrap();
26114    pane.update(cx, |pane, cx| {
26115        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26116        open_editor.update(cx, |editor, cx| {
26117            assert_eq!(
26118                editor.display_text(cx),
26119                "fn main() {}",
26120                "Original main.rs text on initial open",
26121            );
26122        });
26123        assert_eq!(open_editor, main_editor);
26124    });
26125    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
26126
26127    let external_editor = workspace
26128        .update_in(cx, |workspace, window, cx| {
26129            workspace.open_abs_path(
26130                PathBuf::from("/root/foo/bar/external_file.rs"),
26131                OpenOptions::default(),
26132                window,
26133                cx,
26134            )
26135        })
26136        .await
26137        .expect("opening external file")
26138        .downcast::<Editor>()
26139        .expect("downcasted external file's open element to editor");
26140    pane.update(cx, |pane, cx| {
26141        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26142        open_editor.update(cx, |editor, cx| {
26143            assert_eq!(
26144                editor.display_text(cx),
26145                "pub mod external {}",
26146                "External file is open now",
26147            );
26148        });
26149        assert_eq!(open_editor, external_editor);
26150    });
26151    assert_language_servers_count(
26152        1,
26153        "Second, external, *.rs file should join the existing server",
26154        cx,
26155    );
26156
26157    pane.update_in(cx, |pane, window, cx| {
26158        pane.close_active_item(&CloseActiveItem::default(), window, cx)
26159    })
26160    .await
26161    .unwrap();
26162    pane.update_in(cx, |pane, window, cx| {
26163        pane.navigate_backward(&Default::default(), window, cx);
26164    });
26165    cx.run_until_parked();
26166    pane.update(cx, |pane, cx| {
26167        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26168        open_editor.update(cx, |editor, cx| {
26169            assert_eq!(
26170                editor.display_text(cx),
26171                "pub mod external {}",
26172                "External file is open now",
26173            );
26174        });
26175    });
26176    assert_language_servers_count(
26177        1,
26178        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
26179        cx,
26180    );
26181
26182    cx.update(|_, cx| {
26183        workspace::reload(cx);
26184    });
26185    assert_language_servers_count(
26186        1,
26187        "After reloading the worktree with local and external files opened, only one project should be started",
26188        cx,
26189    );
26190}
26191
26192#[gpui::test]
26193async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
26194    init_test(cx, |_| {});
26195
26196    let mut cx = EditorTestContext::new(cx).await;
26197    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26198    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26199
26200    // test cursor move to start of each line on tab
26201    // for `if`, `elif`, `else`, `while`, `with` and `for`
26202    cx.set_state(indoc! {"
26203        def main():
26204        ˇ    for item in items:
26205        ˇ        while item.active:
26206        ˇ            if item.value > 10:
26207        ˇ                continue
26208        ˇ            elif item.value < 0:
26209        ˇ                break
26210        ˇ            else:
26211        ˇ                with item.context() as ctx:
26212        ˇ                    yield count
26213        ˇ        else:
26214        ˇ            log('while else')
26215        ˇ    else:
26216        ˇ        log('for else')
26217    "});
26218    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26219    cx.wait_for_autoindent_applied().await;
26220    cx.assert_editor_state(indoc! {"
26221        def main():
26222            ˇfor item in items:
26223                ˇwhile item.active:
26224                    ˇif item.value > 10:
26225                        ˇcontinue
26226                    ˇelif item.value < 0:
26227                        ˇbreak
26228                    ˇelse:
26229                        ˇwith item.context() as ctx:
26230                            ˇyield count
26231                ˇelse:
26232                    ˇlog('while else')
26233            ˇelse:
26234                ˇlog('for else')
26235    "});
26236    // test relative indent is preserved when tab
26237    // for `if`, `elif`, `else`, `while`, `with` and `for`
26238    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26239    cx.wait_for_autoindent_applied().await;
26240    cx.assert_editor_state(indoc! {"
26241        def main():
26242                ˇfor item in items:
26243                    ˇwhile item.active:
26244                        ˇif item.value > 10:
26245                            ˇcontinue
26246                        ˇelif item.value < 0:
26247                            ˇbreak
26248                        ˇelse:
26249                            ˇwith item.context() as ctx:
26250                                ˇyield count
26251                    ˇelse:
26252                        ˇlog('while else')
26253                ˇelse:
26254                    ˇlog('for else')
26255    "});
26256
26257    // test cursor move to start of each line on tab
26258    // for `try`, `except`, `else`, `finally`, `match` and `def`
26259    cx.set_state(indoc! {"
26260        def main():
26261        ˇ    try:
26262        ˇ        fetch()
26263        ˇ    except ValueError:
26264        ˇ        handle_error()
26265        ˇ    else:
26266        ˇ        match value:
26267        ˇ            case _:
26268        ˇ    finally:
26269        ˇ        def status():
26270        ˇ            return 0
26271    "});
26272    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26273    cx.wait_for_autoindent_applied().await;
26274    cx.assert_editor_state(indoc! {"
26275        def main():
26276            ˇtry:
26277                ˇfetch()
26278            ˇexcept ValueError:
26279                ˇhandle_error()
26280            ˇelse:
26281                ˇmatch value:
26282                    ˇcase _:
26283            ˇfinally:
26284                ˇdef status():
26285                    ˇreturn 0
26286    "});
26287    // test relative indent is preserved when tab
26288    // for `try`, `except`, `else`, `finally`, `match` and `def`
26289    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26290    cx.wait_for_autoindent_applied().await;
26291    cx.assert_editor_state(indoc! {"
26292        def main():
26293                ˇtry:
26294                    ˇfetch()
26295                ˇexcept ValueError:
26296                    ˇhandle_error()
26297                ˇelse:
26298                    ˇmatch value:
26299                        ˇcase _:
26300                ˇfinally:
26301                    ˇdef status():
26302                        ˇreturn 0
26303    "});
26304}
26305
26306#[gpui::test]
26307async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
26308    init_test(cx, |_| {});
26309
26310    let mut cx = EditorTestContext::new(cx).await;
26311    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26312    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26313
26314    // test `else` auto outdents when typed inside `if` block
26315    cx.set_state(indoc! {"
26316        def main():
26317            if i == 2:
26318                return
26319                ˇ
26320    "});
26321    cx.update_editor(|editor, window, cx| {
26322        editor.handle_input("else:", window, cx);
26323    });
26324    cx.wait_for_autoindent_applied().await;
26325    cx.assert_editor_state(indoc! {"
26326        def main():
26327            if i == 2:
26328                return
26329            else:ˇ
26330    "});
26331
26332    // test `except` auto outdents when typed inside `try` block
26333    cx.set_state(indoc! {"
26334        def main():
26335            try:
26336                i = 2
26337                ˇ
26338    "});
26339    cx.update_editor(|editor, window, cx| {
26340        editor.handle_input("except:", window, cx);
26341    });
26342    cx.wait_for_autoindent_applied().await;
26343    cx.assert_editor_state(indoc! {"
26344        def main():
26345            try:
26346                i = 2
26347            except:ˇ
26348    "});
26349
26350    // test `else` auto outdents when typed inside `except` block
26351    cx.set_state(indoc! {"
26352        def main():
26353            try:
26354                i = 2
26355            except:
26356                j = 2
26357                ˇ
26358    "});
26359    cx.update_editor(|editor, window, cx| {
26360        editor.handle_input("else:", window, cx);
26361    });
26362    cx.wait_for_autoindent_applied().await;
26363    cx.assert_editor_state(indoc! {"
26364        def main():
26365            try:
26366                i = 2
26367            except:
26368                j = 2
26369            else:ˇ
26370    "});
26371
26372    // test `finally` auto outdents when typed inside `else` block
26373    cx.set_state(indoc! {"
26374        def main():
26375            try:
26376                i = 2
26377            except:
26378                j = 2
26379            else:
26380                k = 2
26381                ˇ
26382    "});
26383    cx.update_editor(|editor, window, cx| {
26384        editor.handle_input("finally:", window, cx);
26385    });
26386    cx.wait_for_autoindent_applied().await;
26387    cx.assert_editor_state(indoc! {"
26388        def main():
26389            try:
26390                i = 2
26391            except:
26392                j = 2
26393            else:
26394                k = 2
26395            finally:ˇ
26396    "});
26397
26398    // test `else` does not outdents when typed inside `except` block right after for block
26399    cx.set_state(indoc! {"
26400        def main():
26401            try:
26402                i = 2
26403            except:
26404                for i in range(n):
26405                    pass
26406                ˇ
26407    "});
26408    cx.update_editor(|editor, window, cx| {
26409        editor.handle_input("else:", window, cx);
26410    });
26411    cx.wait_for_autoindent_applied().await;
26412    cx.assert_editor_state(indoc! {"
26413        def main():
26414            try:
26415                i = 2
26416            except:
26417                for i in range(n):
26418                    pass
26419                else:ˇ
26420    "});
26421
26422    // test `finally` auto outdents when typed inside `else` block right after for block
26423    cx.set_state(indoc! {"
26424        def main():
26425            try:
26426                i = 2
26427            except:
26428                j = 2
26429            else:
26430                for i in range(n):
26431                    pass
26432                ˇ
26433    "});
26434    cx.update_editor(|editor, window, cx| {
26435        editor.handle_input("finally:", window, cx);
26436    });
26437    cx.wait_for_autoindent_applied().await;
26438    cx.assert_editor_state(indoc! {"
26439        def main():
26440            try:
26441                i = 2
26442            except:
26443                j = 2
26444            else:
26445                for i in range(n):
26446                    pass
26447            finally:ˇ
26448    "});
26449
26450    // test `except` outdents to inner "try" block
26451    cx.set_state(indoc! {"
26452        def main():
26453            try:
26454                i = 2
26455                if i == 2:
26456                    try:
26457                        i = 3
26458                        ˇ
26459    "});
26460    cx.update_editor(|editor, window, cx| {
26461        editor.handle_input("except:", window, cx);
26462    });
26463    cx.wait_for_autoindent_applied().await;
26464    cx.assert_editor_state(indoc! {"
26465        def main():
26466            try:
26467                i = 2
26468                if i == 2:
26469                    try:
26470                        i = 3
26471                    except:ˇ
26472    "});
26473
26474    // test `except` outdents to outer "try" block
26475    cx.set_state(indoc! {"
26476        def main():
26477            try:
26478                i = 2
26479                if i == 2:
26480                    try:
26481                        i = 3
26482                ˇ
26483    "});
26484    cx.update_editor(|editor, window, cx| {
26485        editor.handle_input("except:", window, cx);
26486    });
26487    cx.wait_for_autoindent_applied().await;
26488    cx.assert_editor_state(indoc! {"
26489        def main():
26490            try:
26491                i = 2
26492                if i == 2:
26493                    try:
26494                        i = 3
26495            except:ˇ
26496    "});
26497
26498    // test `else` stays at correct indent when typed after `for` block
26499    cx.set_state(indoc! {"
26500        def main():
26501            for i in range(10):
26502                if i == 3:
26503                    break
26504            ˇ
26505    "});
26506    cx.update_editor(|editor, window, cx| {
26507        editor.handle_input("else:", window, cx);
26508    });
26509    cx.wait_for_autoindent_applied().await;
26510    cx.assert_editor_state(indoc! {"
26511        def main():
26512            for i in range(10):
26513                if i == 3:
26514                    break
26515            else:ˇ
26516    "});
26517
26518    // test does not outdent on typing after line with square brackets
26519    cx.set_state(indoc! {"
26520        def f() -> list[str]:
26521            ˇ
26522    "});
26523    cx.update_editor(|editor, window, cx| {
26524        editor.handle_input("a", window, cx);
26525    });
26526    cx.wait_for_autoindent_applied().await;
26527    cx.assert_editor_state(indoc! {"
26528        def f() -> list[str]:
2652926530    "});
26531
26532    // test does not outdent on typing : after case keyword
26533    cx.set_state(indoc! {"
26534        match 1:
26535            caseˇ
26536    "});
26537    cx.update_editor(|editor, window, cx| {
26538        editor.handle_input(":", window, cx);
26539    });
26540    cx.wait_for_autoindent_applied().await;
26541    cx.assert_editor_state(indoc! {"
26542        match 1:
26543            case:ˇ
26544    "});
26545}
26546
26547#[gpui::test]
26548async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
26549    init_test(cx, |_| {});
26550    update_test_language_settings(cx, |settings| {
26551        settings.defaults.extend_comment_on_newline = Some(false);
26552    });
26553    let mut cx = EditorTestContext::new(cx).await;
26554    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26555    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26556
26557    // test correct indent after newline on comment
26558    cx.set_state(indoc! {"
26559        # COMMENT:ˇ
26560    "});
26561    cx.update_editor(|editor, window, cx| {
26562        editor.newline(&Newline, window, cx);
26563    });
26564    cx.wait_for_autoindent_applied().await;
26565    cx.assert_editor_state(indoc! {"
26566        # COMMENT:
26567        ˇ
26568    "});
26569
26570    // test correct indent after newline in brackets
26571    cx.set_state(indoc! {"
26572        {ˇ}
26573    "});
26574    cx.update_editor(|editor, window, cx| {
26575        editor.newline(&Newline, window, cx);
26576    });
26577    cx.wait_for_autoindent_applied().await;
26578    cx.assert_editor_state(indoc! {"
26579        {
26580            ˇ
26581        }
26582    "});
26583
26584    cx.set_state(indoc! {"
26585        (ˇ)
26586    "});
26587    cx.update_editor(|editor, window, cx| {
26588        editor.newline(&Newline, window, cx);
26589    });
26590    cx.run_until_parked();
26591    cx.assert_editor_state(indoc! {"
26592        (
26593            ˇ
26594        )
26595    "});
26596
26597    // do not indent after empty lists or dictionaries
26598    cx.set_state(indoc! {"
26599        a = []ˇ
26600    "});
26601    cx.update_editor(|editor, window, cx| {
26602        editor.newline(&Newline, window, cx);
26603    });
26604    cx.run_until_parked();
26605    cx.assert_editor_state(indoc! {"
26606        a = []
26607        ˇ
26608    "});
26609}
26610
26611#[gpui::test]
26612async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
26613    init_test(cx, |_| {});
26614
26615    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
26616    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
26617    language_registry.add(markdown_lang());
26618    language_registry.add(python_lang);
26619
26620    let mut cx = EditorTestContext::new(cx).await;
26621    cx.update_buffer(|buffer, cx| {
26622        buffer.set_language_registry(language_registry);
26623        buffer.set_language(Some(markdown_lang()), cx);
26624    });
26625
26626    // Test that `else:` correctly outdents to match `if:` inside the Python code block
26627    cx.set_state(indoc! {"
26628        # Heading
26629
26630        ```python
26631        def main():
26632            if condition:
26633                pass
26634                ˇ
26635        ```
26636    "});
26637    cx.update_editor(|editor, window, cx| {
26638        editor.handle_input("else:", window, cx);
26639    });
26640    cx.run_until_parked();
26641    cx.assert_editor_state(indoc! {"
26642        # Heading
26643
26644        ```python
26645        def main():
26646            if condition:
26647                pass
26648            else:ˇ
26649        ```
26650    "});
26651}
26652
26653#[gpui::test]
26654async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
26655    init_test(cx, |_| {});
26656
26657    let mut cx = EditorTestContext::new(cx).await;
26658    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26659    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26660
26661    // test cursor move to start of each line on tab
26662    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
26663    cx.set_state(indoc! {"
26664        function main() {
26665        ˇ    for item in $items; do
26666        ˇ        while [ -n \"$item\" ]; do
26667        ˇ            if [ \"$value\" -gt 10 ]; then
26668        ˇ                continue
26669        ˇ            elif [ \"$value\" -lt 0 ]; then
26670        ˇ                break
26671        ˇ            else
26672        ˇ                echo \"$item\"
26673        ˇ            fi
26674        ˇ        done
26675        ˇ    done
26676        ˇ}
26677    "});
26678    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26679    cx.wait_for_autoindent_applied().await;
26680    cx.assert_editor_state(indoc! {"
26681        function main() {
26682            ˇfor item in $items; do
26683                ˇwhile [ -n \"$item\" ]; do
26684                    ˇif [ \"$value\" -gt 10 ]; then
26685                        ˇcontinue
26686                    ˇelif [ \"$value\" -lt 0 ]; then
26687                        ˇbreak
26688                    ˇelse
26689                        ˇecho \"$item\"
26690                    ˇfi
26691                ˇdone
26692            ˇdone
26693        ˇ}
26694    "});
26695    // test relative indent is preserved when tab
26696    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26697    cx.wait_for_autoindent_applied().await;
26698    cx.assert_editor_state(indoc! {"
26699        function main() {
26700                ˇfor item in $items; do
26701                    ˇwhile [ -n \"$item\" ]; do
26702                        ˇif [ \"$value\" -gt 10 ]; then
26703                            ˇcontinue
26704                        ˇelif [ \"$value\" -lt 0 ]; then
26705                            ˇbreak
26706                        ˇelse
26707                            ˇecho \"$item\"
26708                        ˇfi
26709                    ˇdone
26710                ˇdone
26711            ˇ}
26712    "});
26713
26714    // test cursor move to start of each line on tab
26715    // for `case` statement with patterns
26716    cx.set_state(indoc! {"
26717        function handle() {
26718        ˇ    case \"$1\" in
26719        ˇ        start)
26720        ˇ            echo \"a\"
26721        ˇ            ;;
26722        ˇ        stop)
26723        ˇ            echo \"b\"
26724        ˇ            ;;
26725        ˇ        *)
26726        ˇ            echo \"c\"
26727        ˇ            ;;
26728        ˇ    esac
26729        ˇ}
26730    "});
26731    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26732    cx.wait_for_autoindent_applied().await;
26733    cx.assert_editor_state(indoc! {"
26734        function handle() {
26735            ˇcase \"$1\" in
26736                ˇstart)
26737                    ˇecho \"a\"
26738                    ˇ;;
26739                ˇstop)
26740                    ˇecho \"b\"
26741                    ˇ;;
26742                ˇ*)
26743                    ˇecho \"c\"
26744                    ˇ;;
26745            ˇesac
26746        ˇ}
26747    "});
26748}
26749
26750#[gpui::test]
26751async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
26752    init_test(cx, |_| {});
26753
26754    let mut cx = EditorTestContext::new(cx).await;
26755    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26756    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26757
26758    // test indents on comment insert
26759    cx.set_state(indoc! {"
26760        function main() {
26761        ˇ    for item in $items; do
26762        ˇ        while [ -n \"$item\" ]; do
26763        ˇ            if [ \"$value\" -gt 10 ]; then
26764        ˇ                continue
26765        ˇ            elif [ \"$value\" -lt 0 ]; then
26766        ˇ                break
26767        ˇ            else
26768        ˇ                echo \"$item\"
26769        ˇ            fi
26770        ˇ        done
26771        ˇ    done
26772        ˇ}
26773    "});
26774    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
26775    cx.wait_for_autoindent_applied().await;
26776    cx.assert_editor_state(indoc! {"
26777        function main() {
26778        #ˇ    for item in $items; do
26779        #ˇ        while [ -n \"$item\" ]; do
26780        #ˇ            if [ \"$value\" -gt 10 ]; then
26781        #ˇ                continue
26782        #ˇ            elif [ \"$value\" -lt 0 ]; then
26783        #ˇ                break
26784        #ˇ            else
26785        #ˇ                echo \"$item\"
26786        #ˇ            fi
26787        #ˇ        done
26788        #ˇ    done
26789        #ˇ}
26790    "});
26791}
26792
26793#[gpui::test]
26794async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
26795    init_test(cx, |_| {});
26796
26797    let mut cx = EditorTestContext::new(cx).await;
26798    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26799    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26800
26801    // test `else` auto outdents when typed inside `if` block
26802    cx.set_state(indoc! {"
26803        if [ \"$1\" = \"test\" ]; then
26804            echo \"foo bar\"
26805            ˇ
26806    "});
26807    cx.update_editor(|editor, window, cx| {
26808        editor.handle_input("else", window, cx);
26809    });
26810    cx.wait_for_autoindent_applied().await;
26811    cx.assert_editor_state(indoc! {"
26812        if [ \"$1\" = \"test\" ]; then
26813            echo \"foo bar\"
26814        elseˇ
26815    "});
26816
26817    // test `elif` auto outdents when typed inside `if` block
26818    cx.set_state(indoc! {"
26819        if [ \"$1\" = \"test\" ]; then
26820            echo \"foo bar\"
26821            ˇ
26822    "});
26823    cx.update_editor(|editor, window, cx| {
26824        editor.handle_input("elif", window, cx);
26825    });
26826    cx.wait_for_autoindent_applied().await;
26827    cx.assert_editor_state(indoc! {"
26828        if [ \"$1\" = \"test\" ]; then
26829            echo \"foo bar\"
26830        elifˇ
26831    "});
26832
26833    // test `fi` auto outdents when typed inside `else` block
26834    cx.set_state(indoc! {"
26835        if [ \"$1\" = \"test\" ]; then
26836            echo \"foo bar\"
26837        else
26838            echo \"bar baz\"
26839            ˇ
26840    "});
26841    cx.update_editor(|editor, window, cx| {
26842        editor.handle_input("fi", window, cx);
26843    });
26844    cx.wait_for_autoindent_applied().await;
26845    cx.assert_editor_state(indoc! {"
26846        if [ \"$1\" = \"test\" ]; then
26847            echo \"foo bar\"
26848        else
26849            echo \"bar baz\"
26850        fiˇ
26851    "});
26852
26853    // test `done` auto outdents when typed inside `while` block
26854    cx.set_state(indoc! {"
26855        while read line; do
26856            echo \"$line\"
26857            ˇ
26858    "});
26859    cx.update_editor(|editor, window, cx| {
26860        editor.handle_input("done", window, cx);
26861    });
26862    cx.wait_for_autoindent_applied().await;
26863    cx.assert_editor_state(indoc! {"
26864        while read line; do
26865            echo \"$line\"
26866        doneˇ
26867    "});
26868
26869    // test `done` auto outdents when typed inside `for` block
26870    cx.set_state(indoc! {"
26871        for file in *.txt; do
26872            cat \"$file\"
26873            ˇ
26874    "});
26875    cx.update_editor(|editor, window, cx| {
26876        editor.handle_input("done", window, cx);
26877    });
26878    cx.wait_for_autoindent_applied().await;
26879    cx.assert_editor_state(indoc! {"
26880        for file in *.txt; do
26881            cat \"$file\"
26882        doneˇ
26883    "});
26884
26885    // test `esac` auto outdents when typed inside `case` block
26886    cx.set_state(indoc! {"
26887        case \"$1\" in
26888            start)
26889                echo \"foo bar\"
26890                ;;
26891            stop)
26892                echo \"bar baz\"
26893                ;;
26894            ˇ
26895    "});
26896    cx.update_editor(|editor, window, cx| {
26897        editor.handle_input("esac", window, cx);
26898    });
26899    cx.wait_for_autoindent_applied().await;
26900    cx.assert_editor_state(indoc! {"
26901        case \"$1\" in
26902            start)
26903                echo \"foo bar\"
26904                ;;
26905            stop)
26906                echo \"bar baz\"
26907                ;;
26908        esacˇ
26909    "});
26910
26911    // test `*)` auto outdents when typed inside `case` block
26912    cx.set_state(indoc! {"
26913        case \"$1\" in
26914            start)
26915                echo \"foo bar\"
26916                ;;
26917                ˇ
26918    "});
26919    cx.update_editor(|editor, window, cx| {
26920        editor.handle_input("*)", window, cx);
26921    });
26922    cx.wait_for_autoindent_applied().await;
26923    cx.assert_editor_state(indoc! {"
26924        case \"$1\" in
26925            start)
26926                echo \"foo bar\"
26927                ;;
26928            *)ˇ
26929    "});
26930
26931    // test `fi` outdents to correct level with nested if blocks
26932    cx.set_state(indoc! {"
26933        if [ \"$1\" = \"test\" ]; then
26934            echo \"outer if\"
26935            if [ \"$2\" = \"debug\" ]; then
26936                echo \"inner if\"
26937                ˇ
26938    "});
26939    cx.update_editor(|editor, window, cx| {
26940        editor.handle_input("fi", window, cx);
26941    });
26942    cx.wait_for_autoindent_applied().await;
26943    cx.assert_editor_state(indoc! {"
26944        if [ \"$1\" = \"test\" ]; then
26945            echo \"outer if\"
26946            if [ \"$2\" = \"debug\" ]; then
26947                echo \"inner if\"
26948            fiˇ
26949    "});
26950}
26951
26952#[gpui::test]
26953async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
26954    init_test(cx, |_| {});
26955    update_test_language_settings(cx, |settings| {
26956        settings.defaults.extend_comment_on_newline = Some(false);
26957    });
26958    let mut cx = EditorTestContext::new(cx).await;
26959    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26960    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26961
26962    // test correct indent after newline on comment
26963    cx.set_state(indoc! {"
26964        # COMMENT:ˇ
26965    "});
26966    cx.update_editor(|editor, window, cx| {
26967        editor.newline(&Newline, window, cx);
26968    });
26969    cx.wait_for_autoindent_applied().await;
26970    cx.assert_editor_state(indoc! {"
26971        # COMMENT:
26972        ˇ
26973    "});
26974
26975    // test correct indent after newline after `then`
26976    cx.set_state(indoc! {"
26977
26978        if [ \"$1\" = \"test\" ]; thenˇ
26979    "});
26980    cx.update_editor(|editor, window, cx| {
26981        editor.newline(&Newline, window, cx);
26982    });
26983    cx.wait_for_autoindent_applied().await;
26984    cx.assert_editor_state(indoc! {"
26985
26986        if [ \"$1\" = \"test\" ]; then
26987            ˇ
26988    "});
26989
26990    // test correct indent after newline after `else`
26991    cx.set_state(indoc! {"
26992        if [ \"$1\" = \"test\" ]; then
26993        elseˇ
26994    "});
26995    cx.update_editor(|editor, window, cx| {
26996        editor.newline(&Newline, window, cx);
26997    });
26998    cx.wait_for_autoindent_applied().await;
26999    cx.assert_editor_state(indoc! {"
27000        if [ \"$1\" = \"test\" ]; then
27001        else
27002            ˇ
27003    "});
27004
27005    // test correct indent after newline after `elif`
27006    cx.set_state(indoc! {"
27007        if [ \"$1\" = \"test\" ]; then
27008        elifˇ
27009    "});
27010    cx.update_editor(|editor, window, cx| {
27011        editor.newline(&Newline, window, cx);
27012    });
27013    cx.wait_for_autoindent_applied().await;
27014    cx.assert_editor_state(indoc! {"
27015        if [ \"$1\" = \"test\" ]; then
27016        elif
27017            ˇ
27018    "});
27019
27020    // test correct indent after newline after `do`
27021    cx.set_state(indoc! {"
27022        for file in *.txt; doˇ
27023    "});
27024    cx.update_editor(|editor, window, cx| {
27025        editor.newline(&Newline, window, cx);
27026    });
27027    cx.wait_for_autoindent_applied().await;
27028    cx.assert_editor_state(indoc! {"
27029        for file in *.txt; do
27030            ˇ
27031    "});
27032
27033    // test correct indent after newline after case pattern
27034    cx.set_state(indoc! {"
27035        case \"$1\" in
27036            start)ˇ
27037    "});
27038    cx.update_editor(|editor, window, cx| {
27039        editor.newline(&Newline, window, cx);
27040    });
27041    cx.wait_for_autoindent_applied().await;
27042    cx.assert_editor_state(indoc! {"
27043        case \"$1\" in
27044            start)
27045                ˇ
27046    "});
27047
27048    // test correct indent after newline after case pattern
27049    cx.set_state(indoc! {"
27050        case \"$1\" in
27051            start)
27052                ;;
27053            *)ˇ
27054    "});
27055    cx.update_editor(|editor, window, cx| {
27056        editor.newline(&Newline, window, cx);
27057    });
27058    cx.wait_for_autoindent_applied().await;
27059    cx.assert_editor_state(indoc! {"
27060        case \"$1\" in
27061            start)
27062                ;;
27063            *)
27064                ˇ
27065    "});
27066
27067    // test correct indent after newline after function opening brace
27068    cx.set_state(indoc! {"
27069        function test() {ˇ}
27070    "});
27071    cx.update_editor(|editor, window, cx| {
27072        editor.newline(&Newline, window, cx);
27073    });
27074    cx.wait_for_autoindent_applied().await;
27075    cx.assert_editor_state(indoc! {"
27076        function test() {
27077            ˇ
27078        }
27079    "});
27080
27081    // test no extra indent after semicolon on same line
27082    cx.set_state(indoc! {"
27083        echo \"test\"27084    "});
27085    cx.update_editor(|editor, window, cx| {
27086        editor.newline(&Newline, window, cx);
27087    });
27088    cx.wait_for_autoindent_applied().await;
27089    cx.assert_editor_state(indoc! {"
27090        echo \"test\";
27091        ˇ
27092    "});
27093}
27094
27095fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
27096    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
27097    point..point
27098}
27099
27100#[track_caller]
27101fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
27102    let (text, ranges) = marked_text_ranges(marked_text, true);
27103    assert_eq!(editor.text(cx), text);
27104    assert_eq!(
27105        editor.selections.ranges(&editor.display_snapshot(cx)),
27106        ranges
27107            .iter()
27108            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
27109            .collect::<Vec<_>>(),
27110        "Assert selections are {}",
27111        marked_text
27112    );
27113}
27114
27115pub fn handle_signature_help_request(
27116    cx: &mut EditorLspTestContext,
27117    mocked_response: lsp::SignatureHelp,
27118) -> impl Future<Output = ()> + use<> {
27119    let mut request =
27120        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
27121            let mocked_response = mocked_response.clone();
27122            async move { Ok(Some(mocked_response)) }
27123        });
27124
27125    async move {
27126        request.next().await;
27127    }
27128}
27129
27130#[track_caller]
27131pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
27132    cx.update_editor(|editor, _, _| {
27133        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
27134            let entries = menu.entries.borrow();
27135            let entries = entries
27136                .iter()
27137                .map(|entry| entry.string.as_str())
27138                .collect::<Vec<_>>();
27139            assert_eq!(entries, expected);
27140        } else {
27141            panic!("Expected completions menu");
27142        }
27143    });
27144}
27145
27146#[gpui::test]
27147async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
27148    init_test(cx, |_| {});
27149    let mut cx = EditorLspTestContext::new_rust(
27150        lsp::ServerCapabilities {
27151            completion_provider: Some(lsp::CompletionOptions {
27152                ..Default::default()
27153            }),
27154            ..Default::default()
27155        },
27156        cx,
27157    )
27158    .await;
27159    cx.lsp
27160        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
27161            Ok(Some(lsp::CompletionResponse::Array(vec![
27162                lsp::CompletionItem {
27163                    label: "unsafe".into(),
27164                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
27165                        range: lsp::Range {
27166                            start: lsp::Position {
27167                                line: 0,
27168                                character: 9,
27169                            },
27170                            end: lsp::Position {
27171                                line: 0,
27172                                character: 11,
27173                            },
27174                        },
27175                        new_text: "unsafe".to_string(),
27176                    })),
27177                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
27178                    ..Default::default()
27179                },
27180            ])))
27181        });
27182
27183    cx.update_editor(|editor, _, cx| {
27184        editor.project().unwrap().update(cx, |project, cx| {
27185            project.snippets().update(cx, |snippets, _cx| {
27186                snippets.add_snippet_for_test(
27187                    None,
27188                    PathBuf::from("test_snippets.json"),
27189                    vec![
27190                        Arc::new(project::snippet_provider::Snippet {
27191                            prefix: vec![
27192                                "unlimited word count".to_string(),
27193                                "unlimit word count".to_string(),
27194                                "unlimited unknown".to_string(),
27195                            ],
27196                            body: "this is many words".to_string(),
27197                            description: Some("description".to_string()),
27198                            name: "multi-word snippet test".to_string(),
27199                        }),
27200                        Arc::new(project::snippet_provider::Snippet {
27201                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
27202                            body: "fewer words".to_string(),
27203                            description: Some("alt description".to_string()),
27204                            name: "other name".to_string(),
27205                        }),
27206                        Arc::new(project::snippet_provider::Snippet {
27207                            prefix: vec!["ab aa".to_string()],
27208                            body: "abcd".to_string(),
27209                            description: None,
27210                            name: "alphabet".to_string(),
27211                        }),
27212                    ],
27213                );
27214            });
27215        })
27216    });
27217
27218    let get_completions = |cx: &mut EditorLspTestContext| {
27219        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
27220            Some(CodeContextMenu::Completions(context_menu)) => {
27221                let entries = context_menu.entries.borrow();
27222                entries
27223                    .iter()
27224                    .map(|entry| entry.string.clone())
27225                    .collect_vec()
27226            }
27227            _ => vec![],
27228        })
27229    };
27230
27231    // snippets:
27232    //  @foo
27233    //  foo bar
27234    //
27235    // when typing:
27236    //
27237    // when typing:
27238    //  - if I type a symbol "open the completions with snippets only"
27239    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
27240    //
27241    // stuff we need:
27242    //  - filtering logic change?
27243    //  - remember how far back the completion started.
27244
27245    let test_cases: &[(&str, &[&str])] = &[
27246        (
27247            "un",
27248            &[
27249                "unsafe",
27250                "unlimit word count",
27251                "unlimited unknown",
27252                "unlimited word count",
27253                "unsnip",
27254            ],
27255        ),
27256        (
27257            "u ",
27258            &[
27259                "unlimit word count",
27260                "unlimited unknown",
27261                "unlimited word count",
27262            ],
27263        ),
27264        ("u a", &["ab aa", "unsafe"]), // unsAfe
27265        (
27266            "u u",
27267            &[
27268                "unsafe",
27269                "unlimit word count",
27270                "unlimited unknown", // ranked highest among snippets
27271                "unlimited word count",
27272                "unsnip",
27273            ],
27274        ),
27275        ("uw c", &["unlimit word count", "unlimited word count"]),
27276        (
27277            "u w",
27278            &[
27279                "unlimit word count",
27280                "unlimited word count",
27281                "unlimited unknown",
27282            ],
27283        ),
27284        ("u w ", &["unlimit word count", "unlimited word count"]),
27285        (
27286            "u ",
27287            &[
27288                "unlimit word count",
27289                "unlimited unknown",
27290                "unlimited word count",
27291            ],
27292        ),
27293        ("wor", &[]),
27294        ("uf", &["unsafe"]),
27295        ("af", &["unsafe"]),
27296        ("afu", &[]),
27297        (
27298            "ue",
27299            &["unsafe", "unlimited unknown", "unlimited word count"],
27300        ),
27301        ("@", &["@few"]),
27302        ("@few", &["@few"]),
27303        ("@ ", &[]),
27304        ("a@", &["@few"]),
27305        ("a@f", &["@few", "unsafe"]),
27306        ("a@fw", &["@few"]),
27307        ("a", &["ab aa", "unsafe"]),
27308        ("aa", &["ab aa"]),
27309        ("aaa", &["ab aa"]),
27310        ("ab", &["ab aa"]),
27311        ("ab ", &["ab aa"]),
27312        ("ab a", &["ab aa", "unsafe"]),
27313        ("ab ab", &["ab aa"]),
27314        ("ab ab aa", &["ab aa"]),
27315    ];
27316
27317    for &(input_to_simulate, expected_completions) in test_cases {
27318        cx.set_state("fn a() { ˇ }\n");
27319        for c in input_to_simulate.split("") {
27320            cx.simulate_input(c);
27321            cx.run_until_parked();
27322        }
27323        let expected_completions = expected_completions
27324            .iter()
27325            .map(|s| s.to_string())
27326            .collect_vec();
27327        assert_eq!(
27328            get_completions(&mut cx),
27329            expected_completions,
27330            "< actual / expected >, input = {input_to_simulate:?}",
27331        );
27332    }
27333}
27334
27335/// Handle completion request passing a marked string specifying where the completion
27336/// should be triggered from using '|' character, what range should be replaced, and what completions
27337/// should be returned using '<' and '>' to delimit the range.
27338///
27339/// Also see `handle_completion_request_with_insert_and_replace`.
27340#[track_caller]
27341pub fn handle_completion_request(
27342    marked_string: &str,
27343    completions: Vec<&'static str>,
27344    is_incomplete: bool,
27345    counter: Arc<AtomicUsize>,
27346    cx: &mut EditorLspTestContext,
27347) -> impl Future<Output = ()> {
27348    let complete_from_marker: TextRangeMarker = '|'.into();
27349    let replace_range_marker: TextRangeMarker = ('<', '>').into();
27350    let (_, mut marked_ranges) = marked_text_ranges_by(
27351        marked_string,
27352        vec![complete_from_marker.clone(), replace_range_marker.clone()],
27353    );
27354
27355    let complete_from_position = cx.to_lsp(MultiBufferOffset(
27356        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
27357    ));
27358    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
27359    let replace_range =
27360        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
27361
27362    let mut request =
27363        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
27364            let completions = completions.clone();
27365            counter.fetch_add(1, atomic::Ordering::Release);
27366            async move {
27367                assert_eq!(params.text_document_position.text_document.uri, url.clone());
27368                assert_eq!(
27369                    params.text_document_position.position,
27370                    complete_from_position
27371                );
27372                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
27373                    is_incomplete,
27374                    item_defaults: None,
27375                    items: completions
27376                        .iter()
27377                        .map(|completion_text| lsp::CompletionItem {
27378                            label: completion_text.to_string(),
27379                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
27380                                range: replace_range,
27381                                new_text: completion_text.to_string(),
27382                            })),
27383                            ..Default::default()
27384                        })
27385                        .collect(),
27386                })))
27387            }
27388        });
27389
27390    async move {
27391        request.next().await;
27392    }
27393}
27394
27395/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
27396/// given instead, which also contains an `insert` range.
27397///
27398/// This function uses markers to define ranges:
27399/// - `|` marks the cursor position
27400/// - `<>` marks the replace range
27401/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
27402pub fn handle_completion_request_with_insert_and_replace(
27403    cx: &mut EditorLspTestContext,
27404    marked_string: &str,
27405    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
27406    counter: Arc<AtomicUsize>,
27407) -> impl Future<Output = ()> {
27408    let complete_from_marker: TextRangeMarker = '|'.into();
27409    let replace_range_marker: TextRangeMarker = ('<', '>').into();
27410    let insert_range_marker: TextRangeMarker = ('{', '}').into();
27411
27412    let (_, mut marked_ranges) = marked_text_ranges_by(
27413        marked_string,
27414        vec![
27415            complete_from_marker.clone(),
27416            replace_range_marker.clone(),
27417            insert_range_marker.clone(),
27418        ],
27419    );
27420
27421    let complete_from_position = cx.to_lsp(MultiBufferOffset(
27422        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
27423    ));
27424    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
27425    let replace_range =
27426        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
27427
27428    let insert_range = match marked_ranges.remove(&insert_range_marker) {
27429        Some(ranges) if !ranges.is_empty() => {
27430            let range1 = ranges[0].clone();
27431            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
27432        }
27433        _ => lsp::Range {
27434            start: replace_range.start,
27435            end: complete_from_position,
27436        },
27437    };
27438
27439    let mut request =
27440        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
27441            let completions = completions.clone();
27442            counter.fetch_add(1, atomic::Ordering::Release);
27443            async move {
27444                assert_eq!(params.text_document_position.text_document.uri, url.clone());
27445                assert_eq!(
27446                    params.text_document_position.position, complete_from_position,
27447                    "marker `|` position doesn't match",
27448                );
27449                Ok(Some(lsp::CompletionResponse::Array(
27450                    completions
27451                        .iter()
27452                        .map(|(label, new_text)| lsp::CompletionItem {
27453                            label: label.to_string(),
27454                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27455                                lsp::InsertReplaceEdit {
27456                                    insert: insert_range,
27457                                    replace: replace_range,
27458                                    new_text: new_text.to_string(),
27459                                },
27460                            )),
27461                            ..Default::default()
27462                        })
27463                        .collect(),
27464                )))
27465            }
27466        });
27467
27468    async move {
27469        request.next().await;
27470    }
27471}
27472
27473fn handle_resolve_completion_request(
27474    cx: &mut EditorLspTestContext,
27475    edits: Option<Vec<(&'static str, &'static str)>>,
27476) -> impl Future<Output = ()> {
27477    let edits = edits.map(|edits| {
27478        edits
27479            .iter()
27480            .map(|(marked_string, new_text)| {
27481                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
27482                let replace_range = cx.to_lsp_range(
27483                    MultiBufferOffset(marked_ranges[0].start)
27484                        ..MultiBufferOffset(marked_ranges[0].end),
27485                );
27486                lsp::TextEdit::new(replace_range, new_text.to_string())
27487            })
27488            .collect::<Vec<_>>()
27489    });
27490
27491    let mut request =
27492        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
27493            let edits = edits.clone();
27494            async move {
27495                Ok(lsp::CompletionItem {
27496                    additional_text_edits: edits,
27497                    ..Default::default()
27498                })
27499            }
27500        });
27501
27502    async move {
27503        request.next().await;
27504    }
27505}
27506
27507pub(crate) fn update_test_language_settings(
27508    cx: &mut TestAppContext,
27509    f: impl Fn(&mut AllLanguageSettingsContent),
27510) {
27511    cx.update(|cx| {
27512        SettingsStore::update_global(cx, |store, cx| {
27513            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
27514        });
27515    });
27516}
27517
27518pub(crate) fn update_test_project_settings(
27519    cx: &mut TestAppContext,
27520    f: impl Fn(&mut ProjectSettingsContent),
27521) {
27522    cx.update(|cx| {
27523        SettingsStore::update_global(cx, |store, cx| {
27524            store.update_user_settings(cx, |settings| f(&mut settings.project));
27525        });
27526    });
27527}
27528
27529pub(crate) fn update_test_editor_settings(
27530    cx: &mut TestAppContext,
27531    f: impl Fn(&mut EditorSettingsContent),
27532) {
27533    cx.update(|cx| {
27534        SettingsStore::update_global(cx, |store, cx| {
27535            store.update_user_settings(cx, |settings| f(&mut settings.editor));
27536        })
27537    })
27538}
27539
27540pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
27541    cx.update(|cx| {
27542        assets::Assets.load_test_fonts(cx);
27543        let store = SettingsStore::test(cx);
27544        cx.set_global(store);
27545        theme::init(theme::LoadThemes::JustBase, cx);
27546        release_channel::init(semver::Version::new(0, 0, 0), cx);
27547        crate::init(cx);
27548    });
27549    zlog::init_test();
27550    update_test_language_settings(cx, f);
27551}
27552
27553#[track_caller]
27554fn assert_hunk_revert(
27555    not_reverted_text_with_selections: &str,
27556    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
27557    expected_reverted_text_with_selections: &str,
27558    base_text: &str,
27559    cx: &mut EditorLspTestContext,
27560) {
27561    cx.set_state(not_reverted_text_with_selections);
27562    cx.set_head_text(base_text);
27563    cx.executor().run_until_parked();
27564
27565    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
27566        let snapshot = editor.snapshot(window, cx);
27567        let reverted_hunk_statuses = snapshot
27568            .buffer_snapshot()
27569            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
27570            .map(|hunk| hunk.status().kind)
27571            .collect::<Vec<_>>();
27572
27573        editor.git_restore(&Default::default(), window, cx);
27574        reverted_hunk_statuses
27575    });
27576    cx.executor().run_until_parked();
27577    cx.assert_editor_state(expected_reverted_text_with_selections);
27578    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
27579}
27580
27581#[gpui::test(iterations = 10)]
27582async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
27583    init_test(cx, |_| {});
27584
27585    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
27586    let counter = diagnostic_requests.clone();
27587
27588    let fs = FakeFs::new(cx.executor());
27589    fs.insert_tree(
27590        path!("/a"),
27591        json!({
27592            "first.rs": "fn main() { let a = 5; }",
27593            "second.rs": "// Test file",
27594        }),
27595    )
27596    .await;
27597
27598    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27599    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27600    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27601
27602    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27603    language_registry.add(rust_lang());
27604    let mut fake_servers = language_registry.register_fake_lsp(
27605        "Rust",
27606        FakeLspAdapter {
27607            capabilities: lsp::ServerCapabilities {
27608                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
27609                    lsp::DiagnosticOptions {
27610                        identifier: None,
27611                        inter_file_dependencies: true,
27612                        workspace_diagnostics: true,
27613                        work_done_progress_options: Default::default(),
27614                    },
27615                )),
27616                ..Default::default()
27617            },
27618            ..Default::default()
27619        },
27620    );
27621
27622    let editor = workspace
27623        .update(cx, |workspace, window, cx| {
27624            workspace.open_abs_path(
27625                PathBuf::from(path!("/a/first.rs")),
27626                OpenOptions::default(),
27627                window,
27628                cx,
27629            )
27630        })
27631        .unwrap()
27632        .await
27633        .unwrap()
27634        .downcast::<Editor>()
27635        .unwrap();
27636    let fake_server = fake_servers.next().await.unwrap();
27637    let server_id = fake_server.server.server_id();
27638    let mut first_request = fake_server
27639        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
27640            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
27641            let result_id = Some(new_result_id.to_string());
27642            assert_eq!(
27643                params.text_document.uri,
27644                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27645            );
27646            async move {
27647                Ok(lsp::DocumentDiagnosticReportResult::Report(
27648                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
27649                        related_documents: None,
27650                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
27651                            items: Vec::new(),
27652                            result_id,
27653                        },
27654                    }),
27655                ))
27656            }
27657        });
27658
27659    let ensure_result_id = |expected_result_id: Option<SharedString>, cx: &mut TestAppContext| {
27660        project.update(cx, |project, cx| {
27661            let buffer_id = editor
27662                .read(cx)
27663                .buffer()
27664                .read(cx)
27665                .as_singleton()
27666                .expect("created a singleton buffer")
27667                .read(cx)
27668                .remote_id();
27669            let buffer_result_id = project
27670                .lsp_store()
27671                .read(cx)
27672                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
27673            assert_eq!(expected_result_id, buffer_result_id);
27674        });
27675    };
27676
27677    ensure_result_id(None, cx);
27678    cx.executor().advance_clock(Duration::from_millis(60));
27679    cx.executor().run_until_parked();
27680    assert_eq!(
27681        diagnostic_requests.load(atomic::Ordering::Acquire),
27682        1,
27683        "Opening file should trigger diagnostic request"
27684    );
27685    first_request
27686        .next()
27687        .await
27688        .expect("should have sent the first diagnostics pull request");
27689    ensure_result_id(Some(SharedString::new_static("1")), cx);
27690
27691    // Editing should trigger diagnostics
27692    editor.update_in(cx, |editor, window, cx| {
27693        editor.handle_input("2", window, cx)
27694    });
27695    cx.executor().advance_clock(Duration::from_millis(60));
27696    cx.executor().run_until_parked();
27697    assert_eq!(
27698        diagnostic_requests.load(atomic::Ordering::Acquire),
27699        2,
27700        "Editing should trigger diagnostic request"
27701    );
27702    ensure_result_id(Some(SharedString::new_static("2")), cx);
27703
27704    // Moving cursor should not trigger diagnostic request
27705    editor.update_in(cx, |editor, window, cx| {
27706        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27707            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
27708        });
27709    });
27710    cx.executor().advance_clock(Duration::from_millis(60));
27711    cx.executor().run_until_parked();
27712    assert_eq!(
27713        diagnostic_requests.load(atomic::Ordering::Acquire),
27714        2,
27715        "Cursor movement should not trigger diagnostic request"
27716    );
27717    ensure_result_id(Some(SharedString::new_static("2")), cx);
27718    // Multiple rapid edits should be debounced
27719    for _ in 0..5 {
27720        editor.update_in(cx, |editor, window, cx| {
27721            editor.handle_input("x", window, cx)
27722        });
27723    }
27724    cx.executor().advance_clock(Duration::from_millis(60));
27725    cx.executor().run_until_parked();
27726
27727    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
27728    assert!(
27729        final_requests <= 4,
27730        "Multiple rapid edits should be debounced (got {final_requests} requests)",
27731    );
27732    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
27733}
27734
27735#[gpui::test]
27736async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
27737    // Regression test for issue #11671
27738    // Previously, adding a cursor after moving multiple cursors would reset
27739    // the cursor count instead of adding to the existing cursors.
27740    init_test(cx, |_| {});
27741    let mut cx = EditorTestContext::new(cx).await;
27742
27743    // Create a simple buffer with cursor at start
27744    cx.set_state(indoc! {"
27745        ˇaaaa
27746        bbbb
27747        cccc
27748        dddd
27749        eeee
27750        ffff
27751        gggg
27752        hhhh"});
27753
27754    // Add 2 cursors below (so we have 3 total)
27755    cx.update_editor(|editor, window, cx| {
27756        editor.add_selection_below(&Default::default(), window, cx);
27757        editor.add_selection_below(&Default::default(), window, cx);
27758    });
27759
27760    // Verify we have 3 cursors
27761    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
27762    assert_eq!(
27763        initial_count, 3,
27764        "Should have 3 cursors after adding 2 below"
27765    );
27766
27767    // Move down one line
27768    cx.update_editor(|editor, window, cx| {
27769        editor.move_down(&MoveDown, window, cx);
27770    });
27771
27772    // Add another cursor below
27773    cx.update_editor(|editor, window, cx| {
27774        editor.add_selection_below(&Default::default(), window, cx);
27775    });
27776
27777    // Should now have 4 cursors (3 original + 1 new)
27778    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
27779    assert_eq!(
27780        final_count, 4,
27781        "Should have 4 cursors after moving and adding another"
27782    );
27783}
27784
27785#[gpui::test]
27786async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
27787    init_test(cx, |_| {});
27788
27789    let mut cx = EditorTestContext::new(cx).await;
27790
27791    cx.set_state(indoc!(
27792        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
27793           Second line here"#
27794    ));
27795
27796    cx.update_editor(|editor, window, cx| {
27797        // Enable soft wrapping with a narrow width to force soft wrapping and
27798        // confirm that more than 2 rows are being displayed.
27799        editor.set_wrap_width(Some(100.0.into()), cx);
27800        assert!(editor.display_text(cx).lines().count() > 2);
27801
27802        editor.add_selection_below(
27803            &AddSelectionBelow {
27804                skip_soft_wrap: true,
27805            },
27806            window,
27807            cx,
27808        );
27809
27810        assert_eq!(
27811            display_ranges(editor, cx),
27812            &[
27813                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27814                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
27815            ]
27816        );
27817
27818        editor.add_selection_above(
27819            &AddSelectionAbove {
27820                skip_soft_wrap: true,
27821            },
27822            window,
27823            cx,
27824        );
27825
27826        assert_eq!(
27827            display_ranges(editor, cx),
27828            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27829        );
27830
27831        editor.add_selection_below(
27832            &AddSelectionBelow {
27833                skip_soft_wrap: false,
27834            },
27835            window,
27836            cx,
27837        );
27838
27839        assert_eq!(
27840            display_ranges(editor, cx),
27841            &[
27842                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27843                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
27844            ]
27845        );
27846
27847        editor.add_selection_above(
27848            &AddSelectionAbove {
27849                skip_soft_wrap: false,
27850            },
27851            window,
27852            cx,
27853        );
27854
27855        assert_eq!(
27856            display_ranges(editor, cx),
27857            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27858        );
27859    });
27860
27861    // Set up text where selections are in the middle of a soft-wrapped line.
27862    // When adding selection below with `skip_soft_wrap` set to `true`, the new
27863    // selection should be at the same buffer column, not the same pixel
27864    // position.
27865    cx.set_state(indoc!(
27866        r#"1. Very long line to show «howˇ» a wrapped line would look
27867           2. Very long line to show how a wrapped line would look"#
27868    ));
27869
27870    cx.update_editor(|editor, window, cx| {
27871        // Enable soft wrapping with a narrow width to force soft wrapping and
27872        // confirm that more than 2 rows are being displayed.
27873        editor.set_wrap_width(Some(100.0.into()), cx);
27874        assert!(editor.display_text(cx).lines().count() > 2);
27875
27876        editor.add_selection_below(
27877            &AddSelectionBelow {
27878                skip_soft_wrap: true,
27879            },
27880            window,
27881            cx,
27882        );
27883
27884        // Assert that there's now 2 selections, both selecting the same column
27885        // range in the buffer row.
27886        let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
27887        let selections = editor.selections.all::<Point>(&display_map);
27888        assert_eq!(selections.len(), 2);
27889        assert_eq!(selections[0].start.column, selections[1].start.column);
27890        assert_eq!(selections[0].end.column, selections[1].end.column);
27891    });
27892}
27893
27894#[gpui::test]
27895async fn test_insert_snippet(cx: &mut TestAppContext) {
27896    init_test(cx, |_| {});
27897    let mut cx = EditorTestContext::new(cx).await;
27898
27899    cx.update_editor(|editor, _, cx| {
27900        editor.project().unwrap().update(cx, |project, cx| {
27901            project.snippets().update(cx, |snippets, _cx| {
27902                let snippet = project::snippet_provider::Snippet {
27903                    prefix: vec![], // no prefix needed!
27904                    body: "an Unspecified".to_string(),
27905                    description: Some("shhhh it's a secret".to_string()),
27906                    name: "super secret snippet".to_string(),
27907                };
27908                snippets.add_snippet_for_test(
27909                    None,
27910                    PathBuf::from("test_snippets.json"),
27911                    vec![Arc::new(snippet)],
27912                );
27913
27914                let snippet = project::snippet_provider::Snippet {
27915                    prefix: vec![], // no prefix needed!
27916                    body: " Location".to_string(),
27917                    description: Some("the word 'location'".to_string()),
27918                    name: "location word".to_string(),
27919                };
27920                snippets.add_snippet_for_test(
27921                    Some("Markdown".to_string()),
27922                    PathBuf::from("test_snippets.json"),
27923                    vec![Arc::new(snippet)],
27924                );
27925            });
27926        })
27927    });
27928
27929    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
27930
27931    cx.update_editor(|editor, window, cx| {
27932        editor.insert_snippet_at_selections(
27933            &InsertSnippet {
27934                language: None,
27935                name: Some("super secret snippet".to_string()),
27936                snippet: None,
27937            },
27938            window,
27939            cx,
27940        );
27941
27942        // Language is specified in the action,
27943        // so the buffer language does not need to match
27944        editor.insert_snippet_at_selections(
27945            &InsertSnippet {
27946                language: Some("Markdown".to_string()),
27947                name: Some("location word".to_string()),
27948                snippet: None,
27949            },
27950            window,
27951            cx,
27952        );
27953
27954        editor.insert_snippet_at_selections(
27955            &InsertSnippet {
27956                language: None,
27957                name: None,
27958                snippet: Some("$0 after".to_string()),
27959            },
27960            window,
27961            cx,
27962        );
27963    });
27964
27965    cx.assert_editor_state(
27966        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
27967    );
27968}
27969
27970#[gpui::test]
27971async fn test_inlay_hints_request_timeout(cx: &mut TestAppContext) {
27972    use crate::inlays::inlay_hints::InlayHintRefreshReason;
27973    use crate::inlays::inlay_hints::tests::{cached_hint_labels, init_test, visible_hint_labels};
27974    use settings::InlayHintSettingsContent;
27975    use std::sync::atomic::AtomicU32;
27976    use std::time::Duration;
27977
27978    const BASE_TIMEOUT_SECS: u64 = 1;
27979
27980    let request_count = Arc::new(AtomicU32::new(0));
27981    let closure_request_count = request_count.clone();
27982
27983    init_test(cx, |settings| {
27984        settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
27985            enabled: Some(true),
27986            ..InlayHintSettingsContent::default()
27987        })
27988    });
27989    cx.update(|cx| {
27990        SettingsStore::update_global(cx, |store, cx| {
27991            store.update_user_settings(cx, |settings| {
27992                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
27993                    request_timeout: Some(BASE_TIMEOUT_SECS),
27994                    button: Some(true),
27995                    notifications: None,
27996                    semantic_token_rules: None,
27997                });
27998            });
27999        });
28000    });
28001
28002    let fs = FakeFs::new(cx.executor());
28003    fs.insert_tree(
28004        path!("/a"),
28005        json!({
28006            "main.rs": "fn main() { let a = 5; }",
28007        }),
28008    )
28009    .await;
28010
28011    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
28012    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28013    language_registry.add(rust_lang());
28014    let mut fake_servers = language_registry.register_fake_lsp(
28015        "Rust",
28016        FakeLspAdapter {
28017            capabilities: lsp::ServerCapabilities {
28018                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
28019                ..lsp::ServerCapabilities::default()
28020            },
28021            initializer: Some(Box::new(move |fake_server| {
28022                let request_count = closure_request_count.clone();
28023                fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
28024                    move |params, cx| {
28025                        let request_count = request_count.clone();
28026                        async move {
28027                            cx.background_executor()
28028                                .timer(Duration::from_secs(BASE_TIMEOUT_SECS * 2))
28029                                .await;
28030                            let count = request_count.fetch_add(1, atomic::Ordering::Release) + 1;
28031                            assert_eq!(
28032                                params.text_document.uri,
28033                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
28034                            );
28035                            Ok(Some(vec![lsp::InlayHint {
28036                                position: lsp::Position::new(0, 1),
28037                                label: lsp::InlayHintLabel::String(count.to_string()),
28038                                kind: None,
28039                                text_edits: None,
28040                                tooltip: None,
28041                                padding_left: None,
28042                                padding_right: None,
28043                                data: None,
28044                            }]))
28045                        }
28046                    },
28047                );
28048            })),
28049            ..FakeLspAdapter::default()
28050        },
28051    );
28052
28053    let buffer = project
28054        .update(cx, |project, cx| {
28055            project.open_local_buffer(path!("/a/main.rs"), cx)
28056        })
28057        .await
28058        .unwrap();
28059    let editor = cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
28060
28061    cx.executor().run_until_parked();
28062    let fake_server = fake_servers.next().await.unwrap();
28063
28064    cx.executor()
28065        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
28066    cx.executor().run_until_parked();
28067    editor
28068        .update(cx, |editor, _window, cx| {
28069            assert!(
28070                cached_hint_labels(editor, cx).is_empty(),
28071                "First request should time out, no hints cached"
28072            );
28073        })
28074        .unwrap();
28075
28076    editor
28077        .update(cx, |editor, _window, cx| {
28078            editor.refresh_inlay_hints(
28079                InlayHintRefreshReason::RefreshRequested {
28080                    server_id: fake_server.server.server_id(),
28081                    request_id: Some(1),
28082                },
28083                cx,
28084            );
28085        })
28086        .unwrap();
28087    cx.executor()
28088        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
28089    cx.executor().run_until_parked();
28090    editor
28091        .update(cx, |editor, _window, cx| {
28092            assert!(
28093                cached_hint_labels(editor, cx).is_empty(),
28094                "Second request should also time out with BASE_TIMEOUT, no hints cached"
28095            );
28096        })
28097        .unwrap();
28098
28099    cx.update(|cx| {
28100        SettingsStore::update_global(cx, |store, cx| {
28101            store.update_user_settings(cx, |settings| {
28102                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
28103                    request_timeout: Some(BASE_TIMEOUT_SECS * 4),
28104                    button: Some(true),
28105                    notifications: None,
28106                    semantic_token_rules: None,
28107                });
28108            });
28109        });
28110    });
28111    editor
28112        .update(cx, |editor, _window, cx| {
28113            editor.refresh_inlay_hints(
28114                InlayHintRefreshReason::RefreshRequested {
28115                    server_id: fake_server.server.server_id(),
28116                    request_id: Some(2),
28117                },
28118                cx,
28119            );
28120        })
28121        .unwrap();
28122    cx.executor()
28123        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS * 4) + Duration::from_millis(100));
28124    cx.executor().run_until_parked();
28125    editor
28126        .update(cx, |editor, _window, cx| {
28127            assert_eq!(
28128                vec!["1".to_string()],
28129                cached_hint_labels(editor, cx),
28130                "With extended timeout (BASE * 4), hints should arrive successfully"
28131            );
28132            assert_eq!(vec!["1".to_string()], visible_hint_labels(editor, cx));
28133        })
28134        .unwrap();
28135}
28136
28137#[gpui::test]
28138async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
28139    init_test(cx, |_| {});
28140    let (editor, cx) = cx.add_window_view(Editor::single_line);
28141    editor.update_in(cx, |editor, window, cx| {
28142        editor.set_text("oops\n\nwow\n", window, cx)
28143    });
28144    cx.run_until_parked();
28145    editor.update(cx, |editor, cx| {
28146        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
28147    });
28148    editor.update(cx, |editor, cx| {
28149        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
28150    });
28151    cx.run_until_parked();
28152    editor.update(cx, |editor, cx| {
28153        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
28154    });
28155}
28156
28157#[gpui::test]
28158async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
28159    init_test(cx, |_| {});
28160
28161    cx.update(|cx| {
28162        register_project_item::<Editor>(cx);
28163    });
28164
28165    let fs = FakeFs::new(cx.executor());
28166    fs.insert_tree("/root1", json!({})).await;
28167    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
28168        .await;
28169
28170    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
28171    let (workspace, cx) =
28172        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
28173
28174    let worktree_id = project.update(cx, |project, cx| {
28175        project.worktrees(cx).next().unwrap().read(cx).id()
28176    });
28177
28178    let handle = workspace
28179        .update_in(cx, |workspace, window, cx| {
28180            let project_path = (worktree_id, rel_path("one.pdf"));
28181            workspace.open_path(project_path, None, true, window, cx)
28182        })
28183        .await
28184        .unwrap();
28185    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
28186    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
28187    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
28188    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
28189}
28190
28191#[gpui::test]
28192async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
28193    init_test(cx, |_| {});
28194
28195    let language = Arc::new(Language::new(
28196        LanguageConfig::default(),
28197        Some(tree_sitter_rust::LANGUAGE.into()),
28198    ));
28199
28200    // Test hierarchical sibling navigation
28201    let text = r#"
28202        fn outer() {
28203            if condition {
28204                let a = 1;
28205            }
28206            let b = 2;
28207        }
28208
28209        fn another() {
28210            let c = 3;
28211        }
28212    "#;
28213
28214    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
28215    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28216    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
28217
28218    // Wait for parsing to complete
28219    editor
28220        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
28221        .await;
28222
28223    editor.update_in(cx, |editor, window, cx| {
28224        // Start by selecting "let a = 1;" inside the if block
28225        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28226            s.select_display_ranges([
28227                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
28228            ]);
28229        });
28230
28231        let initial_selection = editor
28232            .selections
28233            .display_ranges(&editor.display_snapshot(cx));
28234        assert_eq!(initial_selection.len(), 1, "Should have one selection");
28235
28236        // Test select next sibling - should move up levels to find the next sibling
28237        // Since "let a = 1;" has no siblings in the if block, it should move up
28238        // to find "let b = 2;" which is a sibling of the if block
28239        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28240        let next_selection = editor
28241            .selections
28242            .display_ranges(&editor.display_snapshot(cx));
28243
28244        // Should have a selection and it should be different from the initial
28245        assert_eq!(
28246            next_selection.len(),
28247            1,
28248            "Should have one selection after next"
28249        );
28250        assert_ne!(
28251            next_selection[0], initial_selection[0],
28252            "Next sibling selection should be different"
28253        );
28254
28255        // Test hierarchical navigation by going to the end of the current function
28256        // and trying to navigate to the next function
28257        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28258            s.select_display_ranges([
28259                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
28260            ]);
28261        });
28262
28263        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28264        let function_next_selection = editor
28265            .selections
28266            .display_ranges(&editor.display_snapshot(cx));
28267
28268        // Should move to the next function
28269        assert_eq!(
28270            function_next_selection.len(),
28271            1,
28272            "Should have one selection after function next"
28273        );
28274
28275        // Test select previous sibling navigation
28276        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
28277        let prev_selection = editor
28278            .selections
28279            .display_ranges(&editor.display_snapshot(cx));
28280
28281        // Should have a selection and it should be different
28282        assert_eq!(
28283            prev_selection.len(),
28284            1,
28285            "Should have one selection after prev"
28286        );
28287        assert_ne!(
28288            prev_selection[0], function_next_selection[0],
28289            "Previous sibling selection should be different from next"
28290        );
28291    });
28292}
28293
28294#[gpui::test]
28295async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
28296    init_test(cx, |_| {});
28297
28298    let mut cx = EditorTestContext::new(cx).await;
28299    cx.set_state(
28300        "let ˇvariable = 42;
28301let another = variable + 1;
28302let result = variable * 2;",
28303    );
28304
28305    // Set up document highlights manually (simulating LSP response)
28306    cx.update_editor(|editor, _window, cx| {
28307        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
28308
28309        // Create highlights for "variable" occurrences
28310        let highlight_ranges = [
28311            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
28312            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
28313            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
28314        ];
28315
28316        let anchor_ranges: Vec<_> = highlight_ranges
28317            .iter()
28318            .map(|range| range.clone().to_anchors(&buffer_snapshot))
28319            .collect();
28320
28321        editor.highlight_background(
28322            HighlightKey::DocumentHighlightRead,
28323            &anchor_ranges,
28324            |_, theme| theme.colors().editor_document_highlight_read_background,
28325            cx,
28326        );
28327    });
28328
28329    // Go to next highlight - should move to second "variable"
28330    cx.update_editor(|editor, window, cx| {
28331        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28332    });
28333    cx.assert_editor_state(
28334        "let variable = 42;
28335let another = ˇvariable + 1;
28336let result = variable * 2;",
28337    );
28338
28339    // Go to next highlight - should move to third "variable"
28340    cx.update_editor(|editor, window, cx| {
28341        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28342    });
28343    cx.assert_editor_state(
28344        "let variable = 42;
28345let another = variable + 1;
28346let result = ˇvariable * 2;",
28347    );
28348
28349    // Go to next highlight - should stay at third "variable" (no wrap-around)
28350    cx.update_editor(|editor, window, cx| {
28351        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28352    });
28353    cx.assert_editor_state(
28354        "let variable = 42;
28355let another = variable + 1;
28356let result = ˇvariable * 2;",
28357    );
28358
28359    // Now test going backwards from third position
28360    cx.update_editor(|editor, window, cx| {
28361        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28362    });
28363    cx.assert_editor_state(
28364        "let variable = 42;
28365let another = ˇvariable + 1;
28366let result = variable * 2;",
28367    );
28368
28369    // Go to previous highlight - should move to first "variable"
28370    cx.update_editor(|editor, window, cx| {
28371        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28372    });
28373    cx.assert_editor_state(
28374        "let ˇvariable = 42;
28375let another = variable + 1;
28376let result = variable * 2;",
28377    );
28378
28379    // Go to previous highlight - should stay on first "variable"
28380    cx.update_editor(|editor, window, cx| {
28381        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28382    });
28383    cx.assert_editor_state(
28384        "let ˇvariable = 42;
28385let another = variable + 1;
28386let result = variable * 2;",
28387    );
28388}
28389
28390#[gpui::test]
28391async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
28392    cx: &mut gpui::TestAppContext,
28393) {
28394    init_test(cx, |_| {});
28395
28396    let url = "https://zed.dev";
28397
28398    let markdown_language = Arc::new(Language::new(
28399        LanguageConfig {
28400            name: "Markdown".into(),
28401            ..LanguageConfig::default()
28402        },
28403        None,
28404    ));
28405
28406    let mut cx = EditorTestContext::new(cx).await;
28407    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28408    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
28409
28410    cx.update_editor(|editor, window, cx| {
28411        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28412        editor.paste(&Paste, window, cx);
28413    });
28414
28415    cx.assert_editor_state(&format!(
28416        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
28417    ));
28418}
28419
28420#[gpui::test]
28421async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
28422    init_test(cx, |_| {});
28423
28424    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
28425    let mut cx = EditorTestContext::new(cx).await;
28426
28427    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28428
28429    // Case 1: Test if adding a character with multi cursors preserves nested list indents
28430    cx.set_state(&indoc! {"
28431        - [ ] Item 1
28432            - [ ] Item 1.a
28433        - [ˇ] Item 2
28434            - [ˇ] Item 2.a
28435            - [ˇ] Item 2.b
28436        "
28437    });
28438    cx.update_editor(|editor, window, cx| {
28439        editor.handle_input("x", window, cx);
28440    });
28441    cx.run_until_parked();
28442    cx.assert_editor_state(indoc! {"
28443        - [ ] Item 1
28444            - [ ] Item 1.a
28445        - [xˇ] Item 2
28446            - [xˇ] Item 2.a
28447            - [xˇ] Item 2.b
28448        "
28449    });
28450
28451    // Case 2: Test adding new line after nested list continues the list with unchecked task
28452    cx.set_state(&indoc! {"
28453        - [ ] Item 1
28454            - [ ] Item 1.a
28455        - [x] Item 2
28456            - [x] Item 2.a
28457            - [x] Item 2.bˇ"
28458    });
28459    cx.update_editor(|editor, window, cx| {
28460        editor.newline(&Newline, window, cx);
28461    });
28462    cx.assert_editor_state(indoc! {"
28463        - [ ] Item 1
28464            - [ ] Item 1.a
28465        - [x] Item 2
28466            - [x] Item 2.a
28467            - [x] Item 2.b
28468            - [ ] ˇ"
28469    });
28470
28471    // Case 3: Test adding content to continued list item
28472    cx.update_editor(|editor, window, cx| {
28473        editor.handle_input("Item 2.c", window, cx);
28474    });
28475    cx.run_until_parked();
28476    cx.assert_editor_state(indoc! {"
28477        - [ ] Item 1
28478            - [ ] Item 1.a
28479        - [x] Item 2
28480            - [x] Item 2.a
28481            - [x] Item 2.b
28482            - [ ] Item 2.cˇ"
28483    });
28484
28485    // Case 4: Test adding new line after nested ordered list continues with next number
28486    cx.set_state(indoc! {"
28487        1. Item 1
28488            1. Item 1.a
28489        2. Item 2
28490            1. Item 2.a
28491            2. Item 2.bˇ"
28492    });
28493    cx.update_editor(|editor, window, cx| {
28494        editor.newline(&Newline, window, cx);
28495    });
28496    cx.assert_editor_state(indoc! {"
28497        1. Item 1
28498            1. Item 1.a
28499        2. Item 2
28500            1. Item 2.a
28501            2. Item 2.b
28502            3. ˇ"
28503    });
28504
28505    // Case 5: Adding content to continued ordered list item
28506    cx.update_editor(|editor, window, cx| {
28507        editor.handle_input("Item 2.c", window, cx);
28508    });
28509    cx.run_until_parked();
28510    cx.assert_editor_state(indoc! {"
28511        1. Item 1
28512            1. Item 1.a
28513        2. Item 2
28514            1. Item 2.a
28515            2. Item 2.b
28516            3. Item 2.cˇ"
28517    });
28518
28519    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
28520    cx.set_state(indoc! {"
28521        - Item 1
28522            - Item 1.a
28523            - Item 1.a
28524        ˇ"});
28525    cx.update_editor(|editor, window, cx| {
28526        editor.handle_input("-", window, cx);
28527    });
28528    cx.run_until_parked();
28529    cx.assert_editor_state(indoc! {"
28530        - Item 1
28531            - Item 1.a
28532            - Item 1.a
28533"});
28534
28535    // Case 7: Test blockquote newline preserves something
28536    cx.set_state(indoc! {"
28537        > Item 1ˇ"
28538    });
28539    cx.update_editor(|editor, window, cx| {
28540        editor.newline(&Newline, window, cx);
28541    });
28542    cx.assert_editor_state(indoc! {"
28543        > Item 1
28544        ˇ"
28545    });
28546}
28547
28548#[gpui::test]
28549async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
28550    cx: &mut gpui::TestAppContext,
28551) {
28552    init_test(cx, |_| {});
28553
28554    let url = "https://zed.dev";
28555
28556    let markdown_language = Arc::new(Language::new(
28557        LanguageConfig {
28558            name: "Markdown".into(),
28559            ..LanguageConfig::default()
28560        },
28561        None,
28562    ));
28563
28564    let mut cx = EditorTestContext::new(cx).await;
28565    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28566    cx.set_state(&format!(
28567        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
28568    ));
28569
28570    cx.update_editor(|editor, window, cx| {
28571        editor.copy(&Copy, window, cx);
28572    });
28573
28574    cx.set_state(&format!(
28575        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
28576    ));
28577
28578    cx.update_editor(|editor, window, cx| {
28579        editor.paste(&Paste, window, cx);
28580    });
28581
28582    cx.assert_editor_state(&format!(
28583        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
28584    ));
28585}
28586
28587#[gpui::test]
28588async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
28589    cx: &mut gpui::TestAppContext,
28590) {
28591    init_test(cx, |_| {});
28592
28593    let url = "https://zed.dev";
28594
28595    let markdown_language = Arc::new(Language::new(
28596        LanguageConfig {
28597            name: "Markdown".into(),
28598            ..LanguageConfig::default()
28599        },
28600        None,
28601    ));
28602
28603    let mut cx = EditorTestContext::new(cx).await;
28604    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28605    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
28606
28607    cx.update_editor(|editor, window, cx| {
28608        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28609        editor.paste(&Paste, window, cx);
28610    });
28611
28612    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
28613}
28614
28615#[gpui::test]
28616async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
28617    cx: &mut gpui::TestAppContext,
28618) {
28619    init_test(cx, |_| {});
28620
28621    let text = "Awesome";
28622
28623    let markdown_language = Arc::new(Language::new(
28624        LanguageConfig {
28625            name: "Markdown".into(),
28626            ..LanguageConfig::default()
28627        },
28628        None,
28629    ));
28630
28631    let mut cx = EditorTestContext::new(cx).await;
28632    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28633    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
28634
28635    cx.update_editor(|editor, window, cx| {
28636        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
28637        editor.paste(&Paste, window, cx);
28638    });
28639
28640    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
28641}
28642
28643#[gpui::test]
28644async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
28645    cx: &mut gpui::TestAppContext,
28646) {
28647    init_test(cx, |_| {});
28648
28649    let url = "https://zed.dev";
28650
28651    let markdown_language = Arc::new(Language::new(
28652        LanguageConfig {
28653            name: "Rust".into(),
28654            ..LanguageConfig::default()
28655        },
28656        None,
28657    ));
28658
28659    let mut cx = EditorTestContext::new(cx).await;
28660    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28661    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
28662
28663    cx.update_editor(|editor, window, cx| {
28664        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28665        editor.paste(&Paste, window, cx);
28666    });
28667
28668    cx.assert_editor_state(&format!(
28669        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
28670    ));
28671}
28672
28673#[gpui::test]
28674async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
28675    cx: &mut TestAppContext,
28676) {
28677    init_test(cx, |_| {});
28678
28679    let url = "https://zed.dev";
28680
28681    let markdown_language = Arc::new(Language::new(
28682        LanguageConfig {
28683            name: "Markdown".into(),
28684            ..LanguageConfig::default()
28685        },
28686        None,
28687    ));
28688
28689    let (editor, cx) = cx.add_window_view(|window, cx| {
28690        let multi_buffer = MultiBuffer::build_multi(
28691            [
28692                ("this will embed -> link", vec![Point::row_range(0..1)]),
28693                ("this will replace -> link", vec![Point::row_range(0..1)]),
28694            ],
28695            cx,
28696        );
28697        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
28698        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28699            s.select_ranges(vec![
28700                Point::new(0, 19)..Point::new(0, 23),
28701                Point::new(1, 21)..Point::new(1, 25),
28702            ])
28703        });
28704        let first_buffer_id = multi_buffer
28705            .read(cx)
28706            .excerpt_buffer_ids()
28707            .into_iter()
28708            .next()
28709            .unwrap();
28710        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
28711        first_buffer.update(cx, |buffer, cx| {
28712            buffer.set_language(Some(markdown_language.clone()), cx);
28713        });
28714
28715        editor
28716    });
28717    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28718
28719    cx.update_editor(|editor, window, cx| {
28720        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28721        editor.paste(&Paste, window, cx);
28722    });
28723
28724    cx.assert_editor_state(&format!(
28725        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
28726    ));
28727}
28728
28729#[gpui::test]
28730async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
28731    init_test(cx, |_| {});
28732
28733    let fs = FakeFs::new(cx.executor());
28734    fs.insert_tree(
28735        path!("/project"),
28736        json!({
28737            "first.rs": "# First Document\nSome content here.",
28738            "second.rs": "Plain text content for second file.",
28739        }),
28740    )
28741    .await;
28742
28743    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
28744    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
28745    let cx = &mut VisualTestContext::from_window(*workspace, cx);
28746
28747    let language = rust_lang();
28748    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28749    language_registry.add(language.clone());
28750    let mut fake_servers = language_registry.register_fake_lsp(
28751        "Rust",
28752        FakeLspAdapter {
28753            ..FakeLspAdapter::default()
28754        },
28755    );
28756
28757    let buffer1 = project
28758        .update(cx, |project, cx| {
28759            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
28760        })
28761        .await
28762        .unwrap();
28763    let buffer2 = project
28764        .update(cx, |project, cx| {
28765            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
28766        })
28767        .await
28768        .unwrap();
28769
28770    let multi_buffer = cx.new(|cx| {
28771        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
28772        multi_buffer.set_excerpts_for_path(
28773            PathKey::for_buffer(&buffer1, cx),
28774            buffer1.clone(),
28775            [Point::zero()..buffer1.read(cx).max_point()],
28776            3,
28777            cx,
28778        );
28779        multi_buffer.set_excerpts_for_path(
28780            PathKey::for_buffer(&buffer2, cx),
28781            buffer2.clone(),
28782            [Point::zero()..buffer1.read(cx).max_point()],
28783            3,
28784            cx,
28785        );
28786        multi_buffer
28787    });
28788
28789    let (editor, cx) = cx.add_window_view(|window, cx| {
28790        Editor::new(
28791            EditorMode::full(),
28792            multi_buffer,
28793            Some(project.clone()),
28794            window,
28795            cx,
28796        )
28797    });
28798
28799    let fake_language_server = fake_servers.next().await.unwrap();
28800
28801    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
28802
28803    let save = editor.update_in(cx, |editor, window, cx| {
28804        assert!(editor.is_dirty(cx));
28805
28806        editor.save(
28807            SaveOptions {
28808                format: true,
28809                autosave: true,
28810            },
28811            project,
28812            window,
28813            cx,
28814        )
28815    });
28816    let (start_edit_tx, start_edit_rx) = oneshot::channel();
28817    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28818    let mut done_edit_rx = Some(done_edit_rx);
28819    let mut start_edit_tx = Some(start_edit_tx);
28820
28821    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28822        start_edit_tx.take().unwrap().send(()).unwrap();
28823        let done_edit_rx = done_edit_rx.take().unwrap();
28824        async move {
28825            done_edit_rx.await.unwrap();
28826            Ok(None)
28827        }
28828    });
28829
28830    start_edit_rx.await.unwrap();
28831    buffer2
28832        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28833        .unwrap();
28834
28835    done_edit_tx.send(()).unwrap();
28836
28837    save.await.unwrap();
28838    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28839}
28840
28841#[gpui::test]
28842fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28843    init_test(cx, |_| {});
28844
28845    let editor = cx.add_window(|window, cx| {
28846        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28847        build_editor(buffer, window, cx)
28848    });
28849
28850    editor
28851        .update(cx, |editor, window, cx| {
28852            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28853                s.select_display_ranges([
28854                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28855                ])
28856            });
28857
28858            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28859
28860            assert_eq!(
28861                editor.display_text(cx),
28862                "line1\nline2\nline2",
28863                "Duplicating last line upward should create duplicate above, not on same line"
28864            );
28865
28866            assert_eq!(
28867                editor
28868                    .selections
28869                    .display_ranges(&editor.display_snapshot(cx)),
28870                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28871                "Selection should move to the duplicated line"
28872            );
28873        })
28874        .unwrap();
28875}
28876
28877#[gpui::test]
28878async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28879    init_test(cx, |_| {});
28880
28881    let mut cx = EditorTestContext::new(cx).await;
28882
28883    cx.set_state("line1\nline2ˇ");
28884
28885    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28886
28887    let clipboard_text = cx
28888        .read_from_clipboard()
28889        .and_then(|item| item.text().as_deref().map(str::to_string));
28890
28891    assert_eq!(
28892        clipboard_text,
28893        Some("line2\n".to_string()),
28894        "Copying a line without trailing newline should include a newline"
28895    );
28896
28897    cx.set_state("line1\nˇ");
28898
28899    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28900
28901    cx.assert_editor_state("line1\nline2\nˇ");
28902}
28903
28904#[gpui::test]
28905async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28906    init_test(cx, |_| {});
28907
28908    let mut cx = EditorTestContext::new(cx).await;
28909
28910    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28911
28912    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28913
28914    let clipboard_text = cx
28915        .read_from_clipboard()
28916        .and_then(|item| item.text().as_deref().map(str::to_string));
28917
28918    assert_eq!(
28919        clipboard_text,
28920        Some("line1\nline2\nline3\n".to_string()),
28921        "Copying multiple lines should include a single newline between lines"
28922    );
28923
28924    cx.set_state("lineA\nˇ");
28925
28926    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28927
28928    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28929}
28930
28931#[gpui::test]
28932async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28933    init_test(cx, |_| {});
28934
28935    let mut cx = EditorTestContext::new(cx).await;
28936
28937    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28938
28939    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28940
28941    let clipboard_text = cx
28942        .read_from_clipboard()
28943        .and_then(|item| item.text().as_deref().map(str::to_string));
28944
28945    assert_eq!(
28946        clipboard_text,
28947        Some("line1\nline2\nline3\n".to_string()),
28948        "Copying multiple lines should include a single newline between lines"
28949    );
28950
28951    cx.set_state("lineA\nˇ");
28952
28953    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28954
28955    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28956}
28957
28958#[gpui::test]
28959async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28960    init_test(cx, |_| {});
28961
28962    let mut cx = EditorTestContext::new(cx).await;
28963
28964    cx.set_state("line1\nline2ˇ");
28965    cx.update_editor(|e, window, cx| {
28966        e.set_mode(EditorMode::SingleLine);
28967        assert!(e.key_context(window, cx).contains("end_of_input"));
28968    });
28969    cx.set_state("ˇline1\nline2");
28970    cx.update_editor(|e, window, cx| {
28971        assert!(!e.key_context(window, cx).contains("end_of_input"));
28972    });
28973    cx.set_state("line1ˇ\nline2");
28974    cx.update_editor(|e, window, cx| {
28975        assert!(!e.key_context(window, cx).contains("end_of_input"));
28976    });
28977}
28978
28979#[gpui::test]
28980async fn test_sticky_scroll(cx: &mut TestAppContext) {
28981    init_test(cx, |_| {});
28982    let mut cx = EditorTestContext::new(cx).await;
28983
28984    let buffer = indoc! {"
28985            ˇfn foo() {
28986                let abc = 123;
28987            }
28988            struct Bar;
28989            impl Bar {
28990                fn new() -> Self {
28991                    Self
28992                }
28993            }
28994            fn baz() {
28995            }
28996        "};
28997    cx.set_state(&buffer);
28998
28999    cx.update_editor(|e, _, cx| {
29000        e.buffer()
29001            .read(cx)
29002            .as_singleton()
29003            .unwrap()
29004            .update(cx, |buffer, cx| {
29005                buffer.set_language(Some(rust_lang()), cx);
29006            })
29007    });
29008
29009    let mut sticky_headers = |offset: ScrollOffset| {
29010        cx.update_editor(|e, window, cx| {
29011            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
29012        });
29013        cx.run_until_parked();
29014        cx.update_editor(|e, window, cx| {
29015            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
29016                .into_iter()
29017                .map(
29018                    |StickyHeader {
29019                         start_point,
29020                         offset,
29021                         ..
29022                     }| { (start_point, offset) },
29023                )
29024                .collect::<Vec<_>>()
29025        })
29026    };
29027
29028    let fn_foo = Point { row: 0, column: 0 };
29029    let impl_bar = Point { row: 4, column: 0 };
29030    let fn_new = Point { row: 5, column: 4 };
29031
29032    assert_eq!(sticky_headers(0.0), vec![]);
29033    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
29034    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
29035    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
29036    assert_eq!(sticky_headers(2.0), vec![]);
29037    assert_eq!(sticky_headers(2.5), vec![]);
29038    assert_eq!(sticky_headers(3.0), vec![]);
29039    assert_eq!(sticky_headers(3.5), vec![]);
29040    assert_eq!(sticky_headers(4.0), vec![]);
29041    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
29042    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
29043    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
29044    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
29045    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
29046    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
29047    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
29048    assert_eq!(sticky_headers(8.0), vec![]);
29049    assert_eq!(sticky_headers(8.5), vec![]);
29050    assert_eq!(sticky_headers(9.0), vec![]);
29051    assert_eq!(sticky_headers(9.5), vec![]);
29052    assert_eq!(sticky_headers(10.0), vec![]);
29053}
29054
29055#[gpui::test]
29056fn test_relative_line_numbers(cx: &mut TestAppContext) {
29057    init_test(cx, |_| {});
29058
29059    let buffer_1 = cx.new(|cx| Buffer::local("aaaaaaaaaa\nbbb\n", cx));
29060    let buffer_2 = cx.new(|cx| Buffer::local("cccccccccc\nddd\n", cx));
29061    let buffer_3 = cx.new(|cx| Buffer::local("eee\nffffffffff\n", cx));
29062
29063    let multibuffer = cx.new(|cx| {
29064        let mut multibuffer = MultiBuffer::new(ReadWrite);
29065        multibuffer.push_excerpts(
29066            buffer_1.clone(),
29067            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
29068            cx,
29069        );
29070        multibuffer.push_excerpts(
29071            buffer_2.clone(),
29072            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
29073            cx,
29074        );
29075        multibuffer.push_excerpts(
29076            buffer_3.clone(),
29077            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
29078            cx,
29079        );
29080        multibuffer
29081    });
29082
29083    // wrapped contents of multibuffer:
29084    //    aaa
29085    //    aaa
29086    //    aaa
29087    //    a
29088    //    bbb
29089    //
29090    //    ccc
29091    //    ccc
29092    //    ccc
29093    //    c
29094    //    ddd
29095    //
29096    //    eee
29097    //    fff
29098    //    fff
29099    //    fff
29100    //    f
29101
29102    let editor = cx.add_window(|window, cx| build_editor(multibuffer, window, cx));
29103    _ = editor.update(cx, |editor, window, cx| {
29104        editor.set_wrap_width(Some(30.0.into()), cx); // every 3 characters
29105
29106        // includes trailing newlines.
29107        let expected_line_numbers = [2, 6, 7, 10, 14, 15, 18, 19, 23];
29108        let expected_wrapped_line_numbers = [
29109            2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22, 23,
29110        ];
29111
29112        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
29113            s.select_ranges([
29114                Point::new(7, 0)..Point::new(7, 1), // second row of `ccc`
29115            ]);
29116        });
29117
29118        let snapshot = editor.snapshot(window, cx);
29119
29120        // these are all 0-indexed
29121        let base_display_row = DisplayRow(11);
29122        let base_row = 3;
29123        let wrapped_base_row = 7;
29124
29125        // test not counting wrapped lines
29126        let expected_relative_numbers = expected_line_numbers
29127            .into_iter()
29128            .enumerate()
29129            .map(|(i, row)| (DisplayRow(row), i.abs_diff(base_row) as u32))
29130            .filter(|(_, relative_line_number)| *relative_line_number != 0)
29131            .collect_vec();
29132        let actual_relative_numbers = snapshot
29133            .calculate_relative_line_numbers(
29134                &(DisplayRow(0)..DisplayRow(24)),
29135                base_display_row,
29136                false,
29137            )
29138            .into_iter()
29139            .sorted()
29140            .collect_vec();
29141        assert_eq!(expected_relative_numbers, actual_relative_numbers);
29142        // check `calculate_relative_line_numbers()` against `relative_line_delta()` for each line
29143        for (display_row, relative_number) in expected_relative_numbers {
29144            assert_eq!(
29145                relative_number,
29146                snapshot
29147                    .relative_line_delta(display_row, base_display_row, false)
29148                    .unsigned_abs() as u32,
29149            );
29150        }
29151
29152        // test counting wrapped lines
29153        let expected_wrapped_relative_numbers = expected_wrapped_line_numbers
29154            .into_iter()
29155            .enumerate()
29156            .map(|(i, row)| (DisplayRow(row), i.abs_diff(wrapped_base_row) as u32))
29157            .filter(|(row, _)| *row != base_display_row)
29158            .collect_vec();
29159        let actual_relative_numbers = snapshot
29160            .calculate_relative_line_numbers(
29161                &(DisplayRow(0)..DisplayRow(24)),
29162                base_display_row,
29163                true,
29164            )
29165            .into_iter()
29166            .sorted()
29167            .collect_vec();
29168        assert_eq!(expected_wrapped_relative_numbers, actual_relative_numbers);
29169        // check `calculate_relative_line_numbers()` against `relative_wrapped_line_delta()` for each line
29170        for (display_row, relative_number) in expected_wrapped_relative_numbers {
29171            assert_eq!(
29172                relative_number,
29173                snapshot
29174                    .relative_line_delta(display_row, base_display_row, true)
29175                    .unsigned_abs() as u32,
29176            );
29177        }
29178    });
29179}
29180
29181#[gpui::test]
29182async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
29183    init_test(cx, |_| {});
29184    cx.update(|cx| {
29185        SettingsStore::update_global(cx, |store, cx| {
29186            store.update_user_settings(cx, |settings| {
29187                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
29188                    enabled: Some(true),
29189                })
29190            });
29191        });
29192    });
29193    let mut cx = EditorTestContext::new(cx).await;
29194
29195    let line_height = cx.update_editor(|editor, window, cx| {
29196        editor
29197            .style(cx)
29198            .text
29199            .line_height_in_pixels(window.rem_size())
29200    });
29201
29202    let buffer = indoc! {"
29203            ˇfn foo() {
29204                let abc = 123;
29205            }
29206            struct Bar;
29207            impl Bar {
29208                fn new() -> Self {
29209                    Self
29210                }
29211            }
29212            fn baz() {
29213            }
29214        "};
29215    cx.set_state(&buffer);
29216
29217    cx.update_editor(|e, _, cx| {
29218        e.buffer()
29219            .read(cx)
29220            .as_singleton()
29221            .unwrap()
29222            .update(cx, |buffer, cx| {
29223                buffer.set_language(Some(rust_lang()), cx);
29224            })
29225    });
29226
29227    let fn_foo = || empty_range(0, 0);
29228    let impl_bar = || empty_range(4, 0);
29229    let fn_new = || empty_range(5, 4);
29230
29231    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
29232        cx.update_editor(|e, window, cx| {
29233            e.scroll(
29234                gpui::Point {
29235                    x: 0.,
29236                    y: scroll_offset,
29237                },
29238                None,
29239                window,
29240                cx,
29241            );
29242        });
29243        cx.run_until_parked();
29244        cx.simulate_click(
29245            gpui::Point {
29246                x: px(0.),
29247                y: click_offset as f32 * line_height,
29248            },
29249            Modifiers::none(),
29250        );
29251        cx.run_until_parked();
29252        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
29253    };
29254    assert_eq!(
29255        scroll_and_click(
29256            4.5, // impl Bar is halfway off the screen
29257            0.0  // click top of screen
29258        ),
29259        // scrolled to impl Bar
29260        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29261    );
29262
29263    assert_eq!(
29264        scroll_and_click(
29265            4.5,  // impl Bar is halfway off the screen
29266            0.25  // click middle of impl Bar
29267        ),
29268        // scrolled to impl Bar
29269        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29270    );
29271
29272    assert_eq!(
29273        scroll_and_click(
29274            4.5, // impl Bar is halfway off the screen
29275            1.5  // click below impl Bar (e.g. fn new())
29276        ),
29277        // scrolled to fn new() - this is below the impl Bar header which has persisted
29278        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29279    );
29280
29281    assert_eq!(
29282        scroll_and_click(
29283            5.5,  // fn new is halfway underneath impl Bar
29284            0.75  // click on the overlap of impl Bar and fn new()
29285        ),
29286        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29287    );
29288
29289    assert_eq!(
29290        scroll_and_click(
29291            5.5,  // fn new is halfway underneath impl Bar
29292            1.25  // click on the visible part of fn new()
29293        ),
29294        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29295    );
29296
29297    assert_eq!(
29298        scroll_and_click(
29299            1.5, // fn foo is halfway off the screen
29300            0.0  // click top of screen
29301        ),
29302        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
29303    );
29304
29305    assert_eq!(
29306        scroll_and_click(
29307            1.5,  // fn foo is halfway off the screen
29308            0.75  // click visible part of let abc...
29309        )
29310        .0,
29311        // no change in scroll
29312        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
29313        (gpui::Point { x: 0., y: 1.5 })
29314    );
29315}
29316
29317#[gpui::test]
29318async fn test_next_prev_reference(cx: &mut TestAppContext) {
29319    const CYCLE_POSITIONS: &[&'static str] = &[
29320        indoc! {"
29321            fn foo() {
29322                let ˇabc = 123;
29323                let x = abc + 1;
29324                let y = abc + 2;
29325                let z = abc + 2;
29326            }
29327        "},
29328        indoc! {"
29329            fn foo() {
29330                let abc = 123;
29331                let x = ˇabc + 1;
29332                let y = abc + 2;
29333                let z = abc + 2;
29334            }
29335        "},
29336        indoc! {"
29337            fn foo() {
29338                let abc = 123;
29339                let x = abc + 1;
29340                let y = ˇabc + 2;
29341                let z = abc + 2;
29342            }
29343        "},
29344        indoc! {"
29345            fn foo() {
29346                let abc = 123;
29347                let x = abc + 1;
29348                let y = abc + 2;
29349                let z = ˇabc + 2;
29350            }
29351        "},
29352    ];
29353
29354    init_test(cx, |_| {});
29355
29356    let mut cx = EditorLspTestContext::new_rust(
29357        lsp::ServerCapabilities {
29358            references_provider: Some(lsp::OneOf::Left(true)),
29359            ..Default::default()
29360        },
29361        cx,
29362    )
29363    .await;
29364
29365    // importantly, the cursor is in the middle
29366    cx.set_state(indoc! {"
29367        fn foo() {
29368            let aˇbc = 123;
29369            let x = abc + 1;
29370            let y = abc + 2;
29371            let z = abc + 2;
29372        }
29373    "});
29374
29375    let reference_ranges = [
29376        lsp::Position::new(1, 8),
29377        lsp::Position::new(2, 12),
29378        lsp::Position::new(3, 12),
29379        lsp::Position::new(4, 12),
29380    ]
29381    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
29382
29383    cx.lsp
29384        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
29385            Ok(Some(
29386                reference_ranges
29387                    .map(|range| lsp::Location {
29388                        uri: params.text_document_position.text_document.uri.clone(),
29389                        range,
29390                    })
29391                    .to_vec(),
29392            ))
29393        });
29394
29395    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
29396        cx.update_editor(|editor, window, cx| {
29397            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
29398        })
29399        .unwrap()
29400        .await
29401        .unwrap()
29402    };
29403
29404    _move(Direction::Next, 1, &mut cx).await;
29405    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29406
29407    _move(Direction::Next, 1, &mut cx).await;
29408    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29409
29410    _move(Direction::Next, 1, &mut cx).await;
29411    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29412
29413    // loops back to the start
29414    _move(Direction::Next, 1, &mut cx).await;
29415    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29416
29417    // loops back to the end
29418    _move(Direction::Prev, 1, &mut cx).await;
29419    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29420
29421    _move(Direction::Prev, 1, &mut cx).await;
29422    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29423
29424    _move(Direction::Prev, 1, &mut cx).await;
29425    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29426
29427    _move(Direction::Prev, 1, &mut cx).await;
29428    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29429
29430    _move(Direction::Next, 3, &mut cx).await;
29431    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29432
29433    _move(Direction::Prev, 2, &mut cx).await;
29434    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29435}
29436
29437#[gpui::test]
29438async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
29439    init_test(cx, |_| {});
29440
29441    let (editor, cx) = cx.add_window_view(|window, cx| {
29442        let multi_buffer = MultiBuffer::build_multi(
29443            [
29444                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29445                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29446            ],
29447            cx,
29448        );
29449        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29450    });
29451
29452    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29453    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
29454
29455    cx.assert_excerpts_with_selections(indoc! {"
29456        [EXCERPT]
29457        ˇ1
29458        2
29459        3
29460        [EXCERPT]
29461        1
29462        2
29463        3
29464        "});
29465
29466    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
29467    cx.update_editor(|editor, window, cx| {
29468        editor.change_selections(None.into(), window, cx, |s| {
29469            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29470        });
29471    });
29472    cx.assert_excerpts_with_selections(indoc! {"
29473        [EXCERPT]
29474        1
2947529476        3
29477        [EXCERPT]
29478        1
29479        2
29480        3
29481        "});
29482
29483    cx.update_editor(|editor, window, cx| {
29484        editor
29485            .select_all_matches(&SelectAllMatches, window, cx)
29486            .unwrap();
29487    });
29488    cx.assert_excerpts_with_selections(indoc! {"
29489        [EXCERPT]
29490        1
2949129492        3
29493        [EXCERPT]
29494        1
2949529496        3
29497        "});
29498
29499    cx.update_editor(|editor, window, cx| {
29500        editor.handle_input("X", window, cx);
29501    });
29502    cx.assert_excerpts_with_selections(indoc! {"
29503        [EXCERPT]
29504        1
2950529506        3
29507        [EXCERPT]
29508        1
2950929510        3
29511        "});
29512
29513    // Scenario 2: Select "2", then fold second buffer before insertion
29514    cx.update_multibuffer(|mb, cx| {
29515        for buffer_id in buffer_ids.iter() {
29516            let buffer = mb.buffer(*buffer_id).unwrap();
29517            buffer.update(cx, |buffer, cx| {
29518                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29519            });
29520        }
29521    });
29522
29523    // Select "2" and select all matches
29524    cx.update_editor(|editor, window, cx| {
29525        editor.change_selections(None.into(), window, cx, |s| {
29526            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29527        });
29528        editor
29529            .select_all_matches(&SelectAllMatches, window, cx)
29530            .unwrap();
29531    });
29532
29533    // Fold second buffer - should remove selections from folded buffer
29534    cx.update_editor(|editor, _, cx| {
29535        editor.fold_buffer(buffer_ids[1], cx);
29536    });
29537    cx.assert_excerpts_with_selections(indoc! {"
29538        [EXCERPT]
29539        1
2954029541        3
29542        [EXCERPT]
29543        [FOLDED]
29544        "});
29545
29546    // Insert text - should only affect first buffer
29547    cx.update_editor(|editor, window, cx| {
29548        editor.handle_input("Y", window, cx);
29549    });
29550    cx.update_editor(|editor, _, cx| {
29551        editor.unfold_buffer(buffer_ids[1], cx);
29552    });
29553    cx.assert_excerpts_with_selections(indoc! {"
29554        [EXCERPT]
29555        1
2955629557        3
29558        [EXCERPT]
29559        1
29560        2
29561        3
29562        "});
29563
29564    // Scenario 3: Select "2", then fold first buffer before insertion
29565    cx.update_multibuffer(|mb, cx| {
29566        for buffer_id in buffer_ids.iter() {
29567            let buffer = mb.buffer(*buffer_id).unwrap();
29568            buffer.update(cx, |buffer, cx| {
29569                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29570            });
29571        }
29572    });
29573
29574    // Select "2" and select all matches
29575    cx.update_editor(|editor, window, cx| {
29576        editor.change_selections(None.into(), window, cx, |s| {
29577            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29578        });
29579        editor
29580            .select_all_matches(&SelectAllMatches, window, cx)
29581            .unwrap();
29582    });
29583
29584    // Fold first buffer - should remove selections from folded buffer
29585    cx.update_editor(|editor, _, cx| {
29586        editor.fold_buffer(buffer_ids[0], cx);
29587    });
29588    cx.assert_excerpts_with_selections(indoc! {"
29589        [EXCERPT]
29590        [FOLDED]
29591        [EXCERPT]
29592        1
2959329594        3
29595        "});
29596
29597    // Insert text - should only affect second buffer
29598    cx.update_editor(|editor, window, cx| {
29599        editor.handle_input("Z", window, cx);
29600    });
29601    cx.update_editor(|editor, _, cx| {
29602        editor.unfold_buffer(buffer_ids[0], cx);
29603    });
29604    cx.assert_excerpts_with_selections(indoc! {"
29605        [EXCERPT]
29606        1
29607        2
29608        3
29609        [EXCERPT]
29610        1
2961129612        3
29613        "});
29614
29615    // Test correct folded header is selected upon fold
29616    cx.update_editor(|editor, _, cx| {
29617        editor.fold_buffer(buffer_ids[0], cx);
29618        editor.fold_buffer(buffer_ids[1], cx);
29619    });
29620    cx.assert_excerpts_with_selections(indoc! {"
29621        [EXCERPT]
29622        [FOLDED]
29623        [EXCERPT]
29624        ˇ[FOLDED]
29625        "});
29626
29627    // Test selection inside folded buffer unfolds it on type
29628    cx.update_editor(|editor, window, cx| {
29629        editor.handle_input("W", window, cx);
29630    });
29631    cx.update_editor(|editor, _, cx| {
29632        editor.unfold_buffer(buffer_ids[0], cx);
29633    });
29634    cx.assert_excerpts_with_selections(indoc! {"
29635        [EXCERPT]
29636        1
29637        2
29638        3
29639        [EXCERPT]
29640        Wˇ1
29641        Z
29642        3
29643        "});
29644}
29645
29646#[gpui::test]
29647async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
29648    init_test(cx, |_| {});
29649
29650    let (editor, cx) = cx.add_window_view(|window, cx| {
29651        let multi_buffer = MultiBuffer::build_multi(
29652            [
29653                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29654                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
29655            ],
29656            cx,
29657        );
29658        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29659    });
29660
29661    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29662
29663    cx.assert_excerpts_with_selections(indoc! {"
29664        [EXCERPT]
29665        ˇ1
29666        2
29667        3
29668        [EXCERPT]
29669        1
29670        2
29671        3
29672        4
29673        5
29674        6
29675        7
29676        8
29677        9
29678        "});
29679
29680    cx.update_editor(|editor, window, cx| {
29681        editor.change_selections(None.into(), window, cx, |s| {
29682            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
29683        });
29684    });
29685
29686    cx.assert_excerpts_with_selections(indoc! {"
29687        [EXCERPT]
29688        1
29689        2
29690        3
29691        [EXCERPT]
29692        1
29693        2
29694        3
29695        4
29696        5
29697        6
29698        ˇ7
29699        8
29700        9
29701        "});
29702
29703    cx.update_editor(|editor, _window, cx| {
29704        editor.set_vertical_scroll_margin(0, cx);
29705    });
29706
29707    cx.update_editor(|editor, window, cx| {
29708        assert_eq!(editor.vertical_scroll_margin(), 0);
29709        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29710        assert_eq!(
29711            editor.snapshot(window, cx).scroll_position(),
29712            gpui::Point::new(0., 12.0)
29713        );
29714    });
29715
29716    cx.update_editor(|editor, _window, cx| {
29717        editor.set_vertical_scroll_margin(3, cx);
29718    });
29719
29720    cx.update_editor(|editor, window, cx| {
29721        assert_eq!(editor.vertical_scroll_margin(), 3);
29722        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29723        assert_eq!(
29724            editor.snapshot(window, cx).scroll_position(),
29725            gpui::Point::new(0., 9.0)
29726        );
29727    });
29728}
29729
29730#[gpui::test]
29731async fn test_find_references_single_case(cx: &mut TestAppContext) {
29732    init_test(cx, |_| {});
29733    let mut cx = EditorLspTestContext::new_rust(
29734        lsp::ServerCapabilities {
29735            references_provider: Some(lsp::OneOf::Left(true)),
29736            ..lsp::ServerCapabilities::default()
29737        },
29738        cx,
29739    )
29740    .await;
29741
29742    let before = indoc!(
29743        r#"
29744        fn main() {
29745            let aˇbc = 123;
29746            let xyz = abc;
29747        }
29748        "#
29749    );
29750    let after = indoc!(
29751        r#"
29752        fn main() {
29753            let abc = 123;
29754            let xyz = ˇabc;
29755        }
29756        "#
29757    );
29758
29759    cx.lsp
29760        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29761            Ok(Some(vec![
29762                lsp::Location {
29763                    uri: params.text_document_position.text_document.uri.clone(),
29764                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29765                },
29766                lsp::Location {
29767                    uri: params.text_document_position.text_document.uri,
29768                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29769                },
29770            ]))
29771        });
29772
29773    cx.set_state(before);
29774
29775    let action = FindAllReferences {
29776        always_open_multibuffer: false,
29777    };
29778
29779    let navigated = cx
29780        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29781        .expect("should have spawned a task")
29782        .await
29783        .unwrap();
29784
29785    assert_eq!(navigated, Navigated::No);
29786
29787    cx.run_until_parked();
29788
29789    cx.assert_editor_state(after);
29790}
29791
29792#[gpui::test]
29793async fn test_newline_task_list_continuation(cx: &mut TestAppContext) {
29794    init_test(cx, |settings| {
29795        settings.defaults.tab_size = Some(2.try_into().unwrap());
29796    });
29797
29798    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29799    let mut cx = EditorTestContext::new(cx).await;
29800    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29801
29802    // Case 1: Adding newline after (whitespace + prefix + any non-whitespace) adds marker
29803    cx.set_state(indoc! {"
29804        - [ ] taskˇ
29805    "});
29806    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29807    cx.wait_for_autoindent_applied().await;
29808    cx.assert_editor_state(indoc! {"
29809        - [ ] task
29810        - [ ] ˇ
29811    "});
29812
29813    // Case 2: Works with checked task items too
29814    cx.set_state(indoc! {"
29815        - [x] completed taskˇ
29816    "});
29817    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29818    cx.wait_for_autoindent_applied().await;
29819    cx.assert_editor_state(indoc! {"
29820        - [x] completed task
29821        - [ ] ˇ
29822    "});
29823
29824    // Case 2.1: Works with uppercase checked marker too
29825    cx.set_state(indoc! {"
29826        - [X] completed taskˇ
29827    "});
29828    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29829    cx.wait_for_autoindent_applied().await;
29830    cx.assert_editor_state(indoc! {"
29831        - [X] completed task
29832        - [ ] ˇ
29833    "});
29834
29835    // Case 3: Cursor position doesn't matter - content after marker is what counts
29836    cx.set_state(indoc! {"
29837        - [ ] taˇsk
29838    "});
29839    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29840    cx.wait_for_autoindent_applied().await;
29841    cx.assert_editor_state(indoc! {"
29842        - [ ] ta
29843        - [ ] ˇsk
29844    "});
29845
29846    // Case 4: Adding newline after (whitespace + prefix + some whitespace) does NOT add marker
29847    cx.set_state(indoc! {"
29848        - [ ]  ˇ
29849    "});
29850    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29851    cx.wait_for_autoindent_applied().await;
29852    cx.assert_editor_state(
29853        indoc! {"
29854        - [ ]$$
29855        ˇ
29856    "}
29857        .replace("$", " ")
29858        .as_str(),
29859    );
29860
29861    // Case 5: Adding newline with content adds marker preserving indentation
29862    cx.set_state(indoc! {"
29863        - [ ] task
29864          - [ ] indentedˇ
29865    "});
29866    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29867    cx.wait_for_autoindent_applied().await;
29868    cx.assert_editor_state(indoc! {"
29869        - [ ] task
29870          - [ ] indented
29871          - [ ] ˇ
29872    "});
29873
29874    // Case 6: Adding newline with cursor right after prefix, unindents
29875    cx.set_state(indoc! {"
29876        - [ ] task
29877          - [ ] sub task
29878            - [ ] ˇ
29879    "});
29880    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29881    cx.wait_for_autoindent_applied().await;
29882    cx.assert_editor_state(indoc! {"
29883        - [ ] task
29884          - [ ] sub task
29885          - [ ] ˇ
29886    "});
29887    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29888    cx.wait_for_autoindent_applied().await;
29889
29890    // Case 7: Adding newline with cursor right after prefix, removes marker
29891    cx.assert_editor_state(indoc! {"
29892        - [ ] task
29893          - [ ] sub task
29894        - [ ] ˇ
29895    "});
29896    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29897    cx.wait_for_autoindent_applied().await;
29898    cx.assert_editor_state(indoc! {"
29899        - [ ] task
29900          - [ ] sub task
29901        ˇ
29902    "});
29903
29904    // Case 8: Cursor before or inside prefix does not add marker
29905    cx.set_state(indoc! {"
29906        ˇ- [ ] task
29907    "});
29908    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29909    cx.wait_for_autoindent_applied().await;
29910    cx.assert_editor_state(indoc! {"
29911
29912        ˇ- [ ] task
29913    "});
29914
29915    cx.set_state(indoc! {"
29916        - [ˇ ] task
29917    "});
29918    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29919    cx.wait_for_autoindent_applied().await;
29920    cx.assert_editor_state(indoc! {"
29921        - [
29922        ˇ
29923        ] task
29924    "});
29925}
29926
29927#[gpui::test]
29928async fn test_newline_unordered_list_continuation(cx: &mut TestAppContext) {
29929    init_test(cx, |settings| {
29930        settings.defaults.tab_size = Some(2.try_into().unwrap());
29931    });
29932
29933    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29934    let mut cx = EditorTestContext::new(cx).await;
29935    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29936
29937    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) adds marker
29938    cx.set_state(indoc! {"
29939        - itemˇ
29940    "});
29941    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29942    cx.wait_for_autoindent_applied().await;
29943    cx.assert_editor_state(indoc! {"
29944        - item
29945        - ˇ
29946    "});
29947
29948    // Case 2: Works with different markers
29949    cx.set_state(indoc! {"
29950        * starred itemˇ
29951    "});
29952    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29953    cx.wait_for_autoindent_applied().await;
29954    cx.assert_editor_state(indoc! {"
29955        * starred item
29956        * ˇ
29957    "});
29958
29959    cx.set_state(indoc! {"
29960        + plus itemˇ
29961    "});
29962    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29963    cx.wait_for_autoindent_applied().await;
29964    cx.assert_editor_state(indoc! {"
29965        + plus item
29966        + ˇ
29967    "});
29968
29969    // Case 3: Cursor position doesn't matter - content after marker is what counts
29970    cx.set_state(indoc! {"
29971        - itˇem
29972    "});
29973    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29974    cx.wait_for_autoindent_applied().await;
29975    cx.assert_editor_state(indoc! {"
29976        - it
29977        - ˇem
29978    "});
29979
29980    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
29981    cx.set_state(indoc! {"
29982        -  ˇ
29983    "});
29984    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29985    cx.wait_for_autoindent_applied().await;
29986    cx.assert_editor_state(
29987        indoc! {"
29988        - $
29989        ˇ
29990    "}
29991        .replace("$", " ")
29992        .as_str(),
29993    );
29994
29995    // Case 5: Adding newline with content adds marker preserving indentation
29996    cx.set_state(indoc! {"
29997        - item
29998          - indentedˇ
29999    "});
30000    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30001    cx.wait_for_autoindent_applied().await;
30002    cx.assert_editor_state(indoc! {"
30003        - item
30004          - indented
30005          - ˇ
30006    "});
30007
30008    // Case 6: Adding newline with cursor right after marker, unindents
30009    cx.set_state(indoc! {"
30010        - item
30011          - sub item
30012            - ˇ
30013    "});
30014    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30015    cx.wait_for_autoindent_applied().await;
30016    cx.assert_editor_state(indoc! {"
30017        - item
30018          - sub item
30019          - ˇ
30020    "});
30021    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30022    cx.wait_for_autoindent_applied().await;
30023
30024    // Case 7: Adding newline with cursor right after marker, removes marker
30025    cx.assert_editor_state(indoc! {"
30026        - item
30027          - sub item
30028        - ˇ
30029    "});
30030    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30031    cx.wait_for_autoindent_applied().await;
30032    cx.assert_editor_state(indoc! {"
30033        - item
30034          - sub item
30035        ˇ
30036    "});
30037
30038    // Case 8: Cursor before or inside prefix does not add marker
30039    cx.set_state(indoc! {"
30040        ˇ- item
30041    "});
30042    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30043    cx.wait_for_autoindent_applied().await;
30044    cx.assert_editor_state(indoc! {"
30045
30046        ˇ- item
30047    "});
30048
30049    cx.set_state(indoc! {"
30050        -ˇ item
30051    "});
30052    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30053    cx.wait_for_autoindent_applied().await;
30054    cx.assert_editor_state(indoc! {"
30055        -
30056        ˇitem
30057    "});
30058}
30059
30060#[gpui::test]
30061async fn test_newline_ordered_list_continuation(cx: &mut TestAppContext) {
30062    init_test(cx, |settings| {
30063        settings.defaults.tab_size = Some(2.try_into().unwrap());
30064    });
30065
30066    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30067    let mut cx = EditorTestContext::new(cx).await;
30068    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30069
30070    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
30071    cx.set_state(indoc! {"
30072        1. first itemˇ
30073    "});
30074    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30075    cx.wait_for_autoindent_applied().await;
30076    cx.assert_editor_state(indoc! {"
30077        1. first item
30078        2. ˇ
30079    "});
30080
30081    // Case 2: Works with larger numbers
30082    cx.set_state(indoc! {"
30083        10. tenth itemˇ
30084    "});
30085    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30086    cx.wait_for_autoindent_applied().await;
30087    cx.assert_editor_state(indoc! {"
30088        10. tenth item
30089        11. ˇ
30090    "});
30091
30092    // Case 3: Cursor position doesn't matter - content after marker is what counts
30093    cx.set_state(indoc! {"
30094        1. itˇem
30095    "});
30096    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30097    cx.wait_for_autoindent_applied().await;
30098    cx.assert_editor_state(indoc! {"
30099        1. it
30100        2. ˇem
30101    "});
30102
30103    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
30104    cx.set_state(indoc! {"
30105        1.  ˇ
30106    "});
30107    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30108    cx.wait_for_autoindent_applied().await;
30109    cx.assert_editor_state(
30110        indoc! {"
30111        1. $
30112        ˇ
30113    "}
30114        .replace("$", " ")
30115        .as_str(),
30116    );
30117
30118    // Case 5: Adding newline with content adds marker preserving indentation
30119    cx.set_state(indoc! {"
30120        1. item
30121          2. indentedˇ
30122    "});
30123    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30124    cx.wait_for_autoindent_applied().await;
30125    cx.assert_editor_state(indoc! {"
30126        1. item
30127          2. indented
30128          3. ˇ
30129    "});
30130
30131    // Case 6: Adding newline with cursor right after marker, unindents
30132    cx.set_state(indoc! {"
30133        1. item
30134          2. sub item
30135            3. ˇ
30136    "});
30137    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30138    cx.wait_for_autoindent_applied().await;
30139    cx.assert_editor_state(indoc! {"
30140        1. item
30141          2. sub item
30142          1. ˇ
30143    "});
30144    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30145    cx.wait_for_autoindent_applied().await;
30146
30147    // Case 7: Adding newline with cursor right after marker, removes marker
30148    cx.assert_editor_state(indoc! {"
30149        1. item
30150          2. sub item
30151        1. ˇ
30152    "});
30153    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30154    cx.wait_for_autoindent_applied().await;
30155    cx.assert_editor_state(indoc! {"
30156        1. item
30157          2. sub item
30158        ˇ
30159    "});
30160
30161    // Case 8: Cursor before or inside prefix does not add marker
30162    cx.set_state(indoc! {"
30163        ˇ1. item
30164    "});
30165    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30166    cx.wait_for_autoindent_applied().await;
30167    cx.assert_editor_state(indoc! {"
30168
30169        ˇ1. item
30170    "});
30171
30172    cx.set_state(indoc! {"
30173        1ˇ. item
30174    "});
30175    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30176    cx.wait_for_autoindent_applied().await;
30177    cx.assert_editor_state(indoc! {"
30178        1
30179        ˇ. item
30180    "});
30181}
30182
30183#[gpui::test]
30184async fn test_newline_should_not_autoindent_ordered_list(cx: &mut TestAppContext) {
30185    init_test(cx, |settings| {
30186        settings.defaults.tab_size = Some(2.try_into().unwrap());
30187    });
30188
30189    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30190    let mut cx = EditorTestContext::new(cx).await;
30191    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30192
30193    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
30194    cx.set_state(indoc! {"
30195        1. first item
30196          1. sub first item
30197          2. sub second item
30198          3. ˇ
30199    "});
30200    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30201    cx.wait_for_autoindent_applied().await;
30202    cx.assert_editor_state(indoc! {"
30203        1. first item
30204          1. sub first item
30205          2. sub second item
30206        1. ˇ
30207    "});
30208}
30209
30210#[gpui::test]
30211async fn test_tab_list_indent(cx: &mut TestAppContext) {
30212    init_test(cx, |settings| {
30213        settings.defaults.tab_size = Some(2.try_into().unwrap());
30214    });
30215
30216    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30217    let mut cx = EditorTestContext::new(cx).await;
30218    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30219
30220    // Case 1: Unordered list - cursor after prefix, adds indent before prefix
30221    cx.set_state(indoc! {"
30222        - ˇitem
30223    "});
30224    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30225    cx.wait_for_autoindent_applied().await;
30226    let expected = indoc! {"
30227        $$- ˇitem
30228    "};
30229    cx.assert_editor_state(expected.replace("$", " ").as_str());
30230
30231    // Case 2: Task list - cursor after prefix
30232    cx.set_state(indoc! {"
30233        - [ ] ˇtask
30234    "});
30235    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30236    cx.wait_for_autoindent_applied().await;
30237    let expected = indoc! {"
30238        $$- [ ] ˇtask
30239    "};
30240    cx.assert_editor_state(expected.replace("$", " ").as_str());
30241
30242    // Case 3: Ordered list - cursor after prefix
30243    cx.set_state(indoc! {"
30244        1. ˇfirst
30245    "});
30246    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30247    cx.wait_for_autoindent_applied().await;
30248    let expected = indoc! {"
30249        $$1. ˇfirst
30250    "};
30251    cx.assert_editor_state(expected.replace("$", " ").as_str());
30252
30253    // Case 4: With existing indentation - adds more indent
30254    let initial = indoc! {"
30255        $$- ˇitem
30256    "};
30257    cx.set_state(initial.replace("$", " ").as_str());
30258    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30259    cx.wait_for_autoindent_applied().await;
30260    let expected = indoc! {"
30261        $$$$- ˇitem
30262    "};
30263    cx.assert_editor_state(expected.replace("$", " ").as_str());
30264
30265    // Case 5: Empty list item
30266    cx.set_state(indoc! {"
30267        - ˇ
30268    "});
30269    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30270    cx.wait_for_autoindent_applied().await;
30271    let expected = indoc! {"
30272        $$- ˇ
30273    "};
30274    cx.assert_editor_state(expected.replace("$", " ").as_str());
30275
30276    // Case 6: Cursor at end of line with content
30277    cx.set_state(indoc! {"
30278        - itemˇ
30279    "});
30280    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30281    cx.wait_for_autoindent_applied().await;
30282    let expected = indoc! {"
30283        $$- itemˇ
30284    "};
30285    cx.assert_editor_state(expected.replace("$", " ").as_str());
30286
30287    // Case 7: Cursor at start of list item, indents it
30288    cx.set_state(indoc! {"
30289        - item
30290        ˇ  - sub item
30291    "});
30292    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30293    cx.wait_for_autoindent_applied().await;
30294    let expected = indoc! {"
30295        - item
30296          ˇ  - sub item
30297    "};
30298    cx.assert_editor_state(expected);
30299
30300    // Case 8: Cursor at start of list item, moves the cursor when "indent_list_on_tab" is false
30301    cx.update_editor(|_, _, cx| {
30302        SettingsStore::update_global(cx, |store, cx| {
30303            store.update_user_settings(cx, |settings| {
30304                settings.project.all_languages.defaults.indent_list_on_tab = Some(false);
30305            });
30306        });
30307    });
30308    cx.set_state(indoc! {"
30309        - item
30310        ˇ  - sub item
30311    "});
30312    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30313    cx.wait_for_autoindent_applied().await;
30314    let expected = indoc! {"
30315        - item
30316          ˇ- sub item
30317    "};
30318    cx.assert_editor_state(expected);
30319}
30320
30321#[gpui::test]
30322async fn test_local_worktree_trust(cx: &mut TestAppContext) {
30323    init_test(cx, |_| {});
30324    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), cx));
30325
30326    cx.update(|cx| {
30327        SettingsStore::update_global(cx, |store, cx| {
30328            store.update_user_settings(cx, |settings| {
30329                settings.project.all_languages.defaults.inlay_hints =
30330                    Some(InlayHintSettingsContent {
30331                        enabled: Some(true),
30332                        ..InlayHintSettingsContent::default()
30333                    });
30334            });
30335        });
30336    });
30337
30338    let fs = FakeFs::new(cx.executor());
30339    fs.insert_tree(
30340        path!("/project"),
30341        json!({
30342            ".zed": {
30343                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
30344            },
30345            "main.rs": "fn main() {}"
30346        }),
30347    )
30348    .await;
30349
30350    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
30351    let server_name = "override-rust-analyzer";
30352    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
30353
30354    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
30355    language_registry.add(rust_lang());
30356
30357    let capabilities = lsp::ServerCapabilities {
30358        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
30359        ..lsp::ServerCapabilities::default()
30360    };
30361    let mut fake_language_servers = language_registry.register_fake_lsp(
30362        "Rust",
30363        FakeLspAdapter {
30364            name: server_name,
30365            capabilities,
30366            initializer: Some(Box::new({
30367                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30368                move |fake_server| {
30369                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30370                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
30371                        move |_params, _| {
30372                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
30373                            async move {
30374                                Ok(Some(vec![lsp::InlayHint {
30375                                    position: lsp::Position::new(0, 0),
30376                                    label: lsp::InlayHintLabel::String("hint".to_string()),
30377                                    kind: None,
30378                                    text_edits: None,
30379                                    tooltip: None,
30380                                    padding_left: None,
30381                                    padding_right: None,
30382                                    data: None,
30383                                }]))
30384                            }
30385                        },
30386                    );
30387                }
30388            })),
30389            ..FakeLspAdapter::default()
30390        },
30391    );
30392
30393    cx.run_until_parked();
30394
30395    let worktree_id = project.read_with(cx, |project, cx| {
30396        project
30397            .worktrees(cx)
30398            .next()
30399            .map(|wt| wt.read(cx).id())
30400            .expect("should have a worktree")
30401    });
30402    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
30403
30404    let trusted_worktrees =
30405        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
30406
30407    let can_trust = trusted_worktrees.update(cx, |store, cx| {
30408        store.can_trust(&worktree_store, worktree_id, cx)
30409    });
30410    assert!(!can_trust, "worktree should be restricted initially");
30411
30412    let buffer_before_approval = project
30413        .update(cx, |project, cx| {
30414            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
30415        })
30416        .await
30417        .unwrap();
30418
30419    let (editor, cx) = cx.add_window_view(|window, cx| {
30420        Editor::new(
30421            EditorMode::full(),
30422            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
30423            Some(project.clone()),
30424            window,
30425            cx,
30426        )
30427    });
30428    cx.run_until_parked();
30429    let fake_language_server = fake_language_servers.next();
30430
30431    cx.read(|cx| {
30432        let file = buffer_before_approval.read(cx).file();
30433        assert_eq!(
30434            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30435                .language_servers,
30436            ["...".to_string()],
30437            "local .zed/settings.json must not apply before trust approval"
30438        )
30439    });
30440
30441    editor.update_in(cx, |editor, window, cx| {
30442        editor.handle_input("1", window, cx);
30443    });
30444    cx.run_until_parked();
30445    cx.executor()
30446        .advance_clock(std::time::Duration::from_secs(1));
30447    assert_eq!(
30448        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
30449        0,
30450        "inlay hints must not be queried before trust approval"
30451    );
30452
30453    trusted_worktrees.update(cx, |store, cx| {
30454        store.trust(
30455            &worktree_store,
30456            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
30457            cx,
30458        );
30459    });
30460    cx.run_until_parked();
30461
30462    cx.read(|cx| {
30463        let file = buffer_before_approval.read(cx).file();
30464        assert_eq!(
30465            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30466                .language_servers,
30467            ["override-rust-analyzer".to_string()],
30468            "local .zed/settings.json should apply after trust approval"
30469        )
30470    });
30471    let _fake_language_server = fake_language_server.await.unwrap();
30472    editor.update_in(cx, |editor, window, cx| {
30473        editor.handle_input("1", window, cx);
30474    });
30475    cx.run_until_parked();
30476    cx.executor()
30477        .advance_clock(std::time::Duration::from_secs(1));
30478    assert!(
30479        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
30480        "inlay hints should be queried after trust approval"
30481    );
30482
30483    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
30484        store.can_trust(&worktree_store, worktree_id, cx)
30485    });
30486    assert!(can_trust_after, "worktree should be trusted after trust()");
30487}
30488
30489#[gpui::test]
30490fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
30491    // This test reproduces a bug where drawing an editor at a position above the viewport
30492    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
30493    // causes an infinite loop in blocks_in_range.
30494    //
30495    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
30496    // the content mask intersection produces visible_bounds with origin at the viewport top.
30497    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
30498    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
30499    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
30500    init_test(cx, |_| {});
30501
30502    let window = cx.add_window(|_, _| gpui::Empty);
30503    let mut cx = VisualTestContext::from_window(*window, cx);
30504
30505    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
30506    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
30507
30508    // Simulate a small viewport (500x500 pixels at origin 0,0)
30509    cx.simulate_resize(gpui::size(px(500.), px(500.)));
30510
30511    // Draw the editor at a very negative Y position, simulating an editor that's been
30512    // scrolled way above the visible viewport (like in a List that has scrolled past it).
30513    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
30514    // This should NOT hang - it should just render nothing.
30515    cx.draw(
30516        gpui::point(px(0.), px(-10000.)),
30517        gpui::size(px(500.), px(3000.)),
30518        |_, _| editor.clone().into_any_element(),
30519    );
30520
30521    // If we get here without hanging, the test passes
30522}
30523
30524#[gpui::test]
30525async fn test_diff_review_indicator_created_on_gutter_hover(cx: &mut TestAppContext) {
30526    init_test(cx, |_| {});
30527
30528    let fs = FakeFs::new(cx.executor());
30529    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30530        .await;
30531
30532    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30533    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30534    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30535
30536    let editor = workspace
30537        .update(cx, |workspace, window, cx| {
30538            workspace.open_abs_path(
30539                PathBuf::from(path!("/root/file.txt")),
30540                OpenOptions::default(),
30541                window,
30542                cx,
30543            )
30544        })
30545        .unwrap()
30546        .await
30547        .unwrap()
30548        .downcast::<Editor>()
30549        .unwrap();
30550
30551    // Enable diff review button mode
30552    editor.update(cx, |editor, cx| {
30553        editor.set_show_diff_review_button(true, cx);
30554    });
30555
30556    // Initially, no indicator should be present
30557    editor.update(cx, |editor, _cx| {
30558        assert!(
30559            editor.gutter_diff_review_indicator.0.is_none(),
30560            "Indicator should be None initially"
30561        );
30562    });
30563}
30564
30565#[gpui::test]
30566async fn test_diff_review_button_hidden_when_ai_disabled(cx: &mut TestAppContext) {
30567    init_test(cx, |_| {});
30568
30569    // Register DisableAiSettings and set disable_ai to true
30570    cx.update(|cx| {
30571        project::DisableAiSettings::register(cx);
30572        project::DisableAiSettings::override_global(
30573            project::DisableAiSettings { disable_ai: true },
30574            cx,
30575        );
30576    });
30577
30578    let fs = FakeFs::new(cx.executor());
30579    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30580        .await;
30581
30582    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30583    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30584    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30585
30586    let editor = workspace
30587        .update(cx, |workspace, window, cx| {
30588            workspace.open_abs_path(
30589                PathBuf::from(path!("/root/file.txt")),
30590                OpenOptions::default(),
30591                window,
30592                cx,
30593            )
30594        })
30595        .unwrap()
30596        .await
30597        .unwrap()
30598        .downcast::<Editor>()
30599        .unwrap();
30600
30601    // Enable diff review button mode
30602    editor.update(cx, |editor, cx| {
30603        editor.set_show_diff_review_button(true, cx);
30604    });
30605
30606    // Verify AI is disabled
30607    cx.read(|cx| {
30608        assert!(
30609            project::DisableAiSettings::get_global(cx).disable_ai,
30610            "AI should be disabled"
30611        );
30612    });
30613
30614    // The indicator should not be created when AI is disabled
30615    // (The mouse_moved handler checks DisableAiSettings before creating the indicator)
30616    editor.update(cx, |editor, _cx| {
30617        assert!(
30618            editor.gutter_diff_review_indicator.0.is_none(),
30619            "Indicator should be None when AI is disabled"
30620        );
30621    });
30622}
30623
30624#[gpui::test]
30625async fn test_diff_review_button_shown_when_ai_enabled(cx: &mut TestAppContext) {
30626    init_test(cx, |_| {});
30627
30628    // Register DisableAiSettings and set disable_ai to false
30629    cx.update(|cx| {
30630        project::DisableAiSettings::register(cx);
30631        project::DisableAiSettings::override_global(
30632            project::DisableAiSettings { disable_ai: false },
30633            cx,
30634        );
30635    });
30636
30637    let fs = FakeFs::new(cx.executor());
30638    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30639        .await;
30640
30641    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30642    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30643    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30644
30645    let editor = workspace
30646        .update(cx, |workspace, window, cx| {
30647            workspace.open_abs_path(
30648                PathBuf::from(path!("/root/file.txt")),
30649                OpenOptions::default(),
30650                window,
30651                cx,
30652            )
30653        })
30654        .unwrap()
30655        .await
30656        .unwrap()
30657        .downcast::<Editor>()
30658        .unwrap();
30659
30660    // Enable diff review button mode
30661    editor.update(cx, |editor, cx| {
30662        editor.set_show_diff_review_button(true, cx);
30663    });
30664
30665    // Verify AI is enabled
30666    cx.read(|cx| {
30667        assert!(
30668            !project::DisableAiSettings::get_global(cx).disable_ai,
30669            "AI should be enabled"
30670        );
30671    });
30672
30673    // The show_diff_review_button flag should be true
30674    editor.update(cx, |editor, _cx| {
30675        assert!(
30676            editor.show_diff_review_button(),
30677            "show_diff_review_button should be true"
30678        );
30679    });
30680}
30681
30682/// Helper function to create a DiffHunkKey for testing.
30683/// Uses Anchor::min() as a placeholder anchor since these tests don't need
30684/// real buffer positioning.
30685fn test_hunk_key(file_path: &str) -> DiffHunkKey {
30686    DiffHunkKey {
30687        file_path: if file_path.is_empty() {
30688            Arc::from(util::rel_path::RelPath::empty())
30689        } else {
30690            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
30691        },
30692        hunk_start_anchor: Anchor::min(),
30693    }
30694}
30695
30696/// Helper function to create a DiffHunkKey with a specific anchor for testing.
30697fn test_hunk_key_with_anchor(file_path: &str, anchor: Anchor) -> DiffHunkKey {
30698    DiffHunkKey {
30699        file_path: if file_path.is_empty() {
30700            Arc::from(util::rel_path::RelPath::empty())
30701        } else {
30702            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
30703        },
30704        hunk_start_anchor: anchor,
30705    }
30706}
30707
30708/// Helper function to add a review comment with default anchors for testing.
30709fn add_test_comment(
30710    editor: &mut Editor,
30711    key: DiffHunkKey,
30712    comment: &str,
30713    cx: &mut Context<Editor>,
30714) -> usize {
30715    editor.add_review_comment(key, comment.to_string(), Anchor::min()..Anchor::max(), cx)
30716}
30717
30718#[gpui::test]
30719fn test_review_comment_add_to_hunk(cx: &mut TestAppContext) {
30720    init_test(cx, |_| {});
30721
30722    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30723
30724    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30725        let key = test_hunk_key("");
30726
30727        let id = add_test_comment(editor, key.clone(), "Test comment", cx);
30728
30729        let snapshot = editor.buffer().read(cx).snapshot(cx);
30730        assert_eq!(editor.total_review_comment_count(), 1);
30731        assert_eq!(editor.hunk_comment_count(&key, &snapshot), 1);
30732
30733        let comments = editor.comments_for_hunk(&key, &snapshot);
30734        assert_eq!(comments.len(), 1);
30735        assert_eq!(comments[0].comment, "Test comment");
30736        assert_eq!(comments[0].id, id);
30737    });
30738}
30739
30740#[gpui::test]
30741fn test_review_comments_are_per_hunk(cx: &mut TestAppContext) {
30742    init_test(cx, |_| {});
30743
30744    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30745
30746    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30747        let snapshot = editor.buffer().read(cx).snapshot(cx);
30748        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
30749        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
30750        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
30751        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
30752
30753        add_test_comment(editor, key1.clone(), "Comment for file1", cx);
30754        add_test_comment(editor, key2.clone(), "Comment for file2", cx);
30755
30756        let snapshot = editor.buffer().read(cx).snapshot(cx);
30757        assert_eq!(editor.total_review_comment_count(), 2);
30758        assert_eq!(editor.hunk_comment_count(&key1, &snapshot), 1);
30759        assert_eq!(editor.hunk_comment_count(&key2, &snapshot), 1);
30760
30761        assert_eq!(
30762            editor.comments_for_hunk(&key1, &snapshot)[0].comment,
30763            "Comment for file1"
30764        );
30765        assert_eq!(
30766            editor.comments_for_hunk(&key2, &snapshot)[0].comment,
30767            "Comment for file2"
30768        );
30769    });
30770}
30771
30772#[gpui::test]
30773fn test_review_comment_remove(cx: &mut TestAppContext) {
30774    init_test(cx, |_| {});
30775
30776    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30777
30778    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30779        let key = test_hunk_key("");
30780
30781        let id = add_test_comment(editor, key, "To be removed", cx);
30782
30783        assert_eq!(editor.total_review_comment_count(), 1);
30784
30785        let removed = editor.remove_review_comment(id, cx);
30786        assert!(removed);
30787        assert_eq!(editor.total_review_comment_count(), 0);
30788
30789        // Try to remove again
30790        let removed_again = editor.remove_review_comment(id, cx);
30791        assert!(!removed_again);
30792    });
30793}
30794
30795#[gpui::test]
30796fn test_review_comment_update(cx: &mut TestAppContext) {
30797    init_test(cx, |_| {});
30798
30799    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30800
30801    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30802        let key = test_hunk_key("");
30803
30804        let id = add_test_comment(editor, key.clone(), "Original text", cx);
30805
30806        let updated = editor.update_review_comment(id, "Updated text".to_string(), cx);
30807        assert!(updated);
30808
30809        let snapshot = editor.buffer().read(cx).snapshot(cx);
30810        let comments = editor.comments_for_hunk(&key, &snapshot);
30811        assert_eq!(comments[0].comment, "Updated text");
30812        assert!(!comments[0].is_editing); // Should clear editing flag
30813    });
30814}
30815
30816#[gpui::test]
30817fn test_review_comment_take_all(cx: &mut TestAppContext) {
30818    init_test(cx, |_| {});
30819
30820    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30821
30822    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30823        let snapshot = editor.buffer().read(cx).snapshot(cx);
30824        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
30825        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
30826        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
30827        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
30828
30829        let id1 = add_test_comment(editor, key1.clone(), "Comment 1", cx);
30830        let id2 = add_test_comment(editor, key1.clone(), "Comment 2", cx);
30831        let id3 = add_test_comment(editor, key2.clone(), "Comment 3", cx);
30832
30833        // IDs should be sequential starting from 0
30834        assert_eq!(id1, 0);
30835        assert_eq!(id2, 1);
30836        assert_eq!(id3, 2);
30837
30838        assert_eq!(editor.total_review_comment_count(), 3);
30839
30840        let taken = editor.take_all_review_comments(cx);
30841
30842        // Should have 2 entries (one per hunk)
30843        assert_eq!(taken.len(), 2);
30844
30845        // Total comments should be 3
30846        let total: usize = taken
30847            .iter()
30848            .map(|(_, comments): &(DiffHunkKey, Vec<StoredReviewComment>)| comments.len())
30849            .sum();
30850        assert_eq!(total, 3);
30851
30852        // Storage should be empty
30853        assert_eq!(editor.total_review_comment_count(), 0);
30854
30855        // After taking all comments, ID counter should reset
30856        // New comments should get IDs starting from 0 again
30857        let new_id1 = add_test_comment(editor, key1, "New Comment 1", cx);
30858        let new_id2 = add_test_comment(editor, key2, "New Comment 2", cx);
30859
30860        assert_eq!(new_id1, 0, "ID counter should reset after take_all");
30861        assert_eq!(new_id2, 1, "IDs should be sequential after reset");
30862    });
30863}
30864
30865#[gpui::test]
30866fn test_diff_review_overlay_show_and_dismiss(cx: &mut TestAppContext) {
30867    init_test(cx, |_| {});
30868
30869    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30870
30871    // Show overlay
30872    editor
30873        .update(cx, |editor, window, cx| {
30874            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
30875        })
30876        .unwrap();
30877
30878    // Verify overlay is shown
30879    editor
30880        .update(cx, |editor, _window, cx| {
30881            assert!(!editor.diff_review_overlays.is_empty());
30882            assert_eq!(editor.diff_review_line_range(cx), Some((0, 0)));
30883            assert!(editor.diff_review_prompt_editor().is_some());
30884        })
30885        .unwrap();
30886
30887    // Dismiss overlay
30888    editor
30889        .update(cx, |editor, _window, cx| {
30890            editor.dismiss_all_diff_review_overlays(cx);
30891        })
30892        .unwrap();
30893
30894    // Verify overlay is dismissed
30895    editor
30896        .update(cx, |editor, _window, cx| {
30897            assert!(editor.diff_review_overlays.is_empty());
30898            assert_eq!(editor.diff_review_line_range(cx), None);
30899            assert!(editor.diff_review_prompt_editor().is_none());
30900        })
30901        .unwrap();
30902}
30903
30904#[gpui::test]
30905fn test_diff_review_overlay_dismiss_via_cancel(cx: &mut TestAppContext) {
30906    init_test(cx, |_| {});
30907
30908    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30909
30910    // Show overlay
30911    editor
30912        .update(cx, |editor, window, cx| {
30913            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
30914        })
30915        .unwrap();
30916
30917    // Verify overlay is shown
30918    editor
30919        .update(cx, |editor, _window, _cx| {
30920            assert!(!editor.diff_review_overlays.is_empty());
30921        })
30922        .unwrap();
30923
30924    // Dismiss via dismiss_menus_and_popups (which is called by cancel action)
30925    editor
30926        .update(cx, |editor, window, cx| {
30927            editor.dismiss_menus_and_popups(true, window, cx);
30928        })
30929        .unwrap();
30930
30931    // Verify overlay is dismissed
30932    editor
30933        .update(cx, |editor, _window, _cx| {
30934            assert!(editor.diff_review_overlays.is_empty());
30935        })
30936        .unwrap();
30937}
30938
30939#[gpui::test]
30940fn test_diff_review_empty_comment_not_submitted(cx: &mut TestAppContext) {
30941    init_test(cx, |_| {});
30942
30943    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30944
30945    // Show overlay
30946    editor
30947        .update(cx, |editor, window, cx| {
30948            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
30949        })
30950        .unwrap();
30951
30952    // Try to submit without typing anything (empty comment)
30953    editor
30954        .update(cx, |editor, window, cx| {
30955            editor.submit_diff_review_comment(window, cx);
30956        })
30957        .unwrap();
30958
30959    // Verify no comment was added
30960    editor
30961        .update(cx, |editor, _window, _cx| {
30962            assert_eq!(editor.total_review_comment_count(), 0);
30963        })
30964        .unwrap();
30965
30966    // Try to submit with whitespace-only comment
30967    editor
30968        .update(cx, |editor, window, cx| {
30969            if let Some(prompt_editor) = editor.diff_review_prompt_editor().cloned() {
30970                prompt_editor.update(cx, |pe, cx| {
30971                    pe.insert("   \n\t  ", window, cx);
30972                });
30973            }
30974            editor.submit_diff_review_comment(window, cx);
30975        })
30976        .unwrap();
30977
30978    // Verify still no comment was added
30979    editor
30980        .update(cx, |editor, _window, _cx| {
30981            assert_eq!(editor.total_review_comment_count(), 0);
30982        })
30983        .unwrap();
30984}
30985
30986#[gpui::test]
30987fn test_diff_review_inline_edit_flow(cx: &mut TestAppContext) {
30988    init_test(cx, |_| {});
30989
30990    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30991
30992    // Add a comment directly
30993    let comment_id = editor
30994        .update(cx, |editor, _window, cx| {
30995            let key = test_hunk_key("");
30996            add_test_comment(editor, key, "Original comment", cx)
30997        })
30998        .unwrap();
30999
31000    // Set comment to editing mode
31001    editor
31002        .update(cx, |editor, _window, cx| {
31003            editor.set_comment_editing(comment_id, true, cx);
31004        })
31005        .unwrap();
31006
31007    // Verify editing flag is set
31008    editor
31009        .update(cx, |editor, _window, cx| {
31010            let key = test_hunk_key("");
31011            let snapshot = editor.buffer().read(cx).snapshot(cx);
31012            let comments = editor.comments_for_hunk(&key, &snapshot);
31013            assert_eq!(comments.len(), 1);
31014            assert!(comments[0].is_editing);
31015        })
31016        .unwrap();
31017
31018    // Update the comment
31019    editor
31020        .update(cx, |editor, _window, cx| {
31021            let updated =
31022                editor.update_review_comment(comment_id, "Updated comment".to_string(), cx);
31023            assert!(updated);
31024        })
31025        .unwrap();
31026
31027    // Verify comment was updated and editing flag is cleared
31028    editor
31029        .update(cx, |editor, _window, cx| {
31030            let key = test_hunk_key("");
31031            let snapshot = editor.buffer().read(cx).snapshot(cx);
31032            let comments = editor.comments_for_hunk(&key, &snapshot);
31033            assert_eq!(comments[0].comment, "Updated comment");
31034            assert!(!comments[0].is_editing);
31035        })
31036        .unwrap();
31037}
31038
31039#[gpui::test]
31040fn test_orphaned_comments_are_cleaned_up(cx: &mut TestAppContext) {
31041    init_test(cx, |_| {});
31042
31043    // Create an editor with some text
31044    let editor = cx.add_window(|window, cx| {
31045        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
31046        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31047        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31048    });
31049
31050    // Add a comment with an anchor on line 2
31051    editor
31052        .update(cx, |editor, _window, cx| {
31053            let snapshot = editor.buffer().read(cx).snapshot(cx);
31054            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
31055            let key = DiffHunkKey {
31056                file_path: Arc::from(util::rel_path::RelPath::empty()),
31057                hunk_start_anchor: anchor,
31058            };
31059            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
31060            assert_eq!(editor.total_review_comment_count(), 1);
31061        })
31062        .unwrap();
31063
31064    // Delete all content (this should orphan the comment's anchor)
31065    editor
31066        .update(cx, |editor, window, cx| {
31067            editor.select_all(&SelectAll, window, cx);
31068            editor.insert("completely new content", window, cx);
31069        })
31070        .unwrap();
31071
31072    // Trigger cleanup
31073    editor
31074        .update(cx, |editor, _window, cx| {
31075            editor.cleanup_orphaned_review_comments(cx);
31076            // Comment should be removed because its anchor is invalid
31077            assert_eq!(editor.total_review_comment_count(), 0);
31078        })
31079        .unwrap();
31080}
31081
31082#[gpui::test]
31083fn test_orphaned_comments_cleanup_called_on_buffer_edit(cx: &mut TestAppContext) {
31084    init_test(cx, |_| {});
31085
31086    // Create an editor with some text
31087    let editor = cx.add_window(|window, cx| {
31088        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
31089        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31090        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31091    });
31092
31093    // Add a comment with an anchor on line 2
31094    editor
31095        .update(cx, |editor, _window, cx| {
31096            let snapshot = editor.buffer().read(cx).snapshot(cx);
31097            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
31098            let key = DiffHunkKey {
31099                file_path: Arc::from(util::rel_path::RelPath::empty()),
31100                hunk_start_anchor: anchor,
31101            };
31102            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
31103            assert_eq!(editor.total_review_comment_count(), 1);
31104        })
31105        .unwrap();
31106
31107    // Edit the buffer - this should trigger cleanup via on_buffer_event
31108    // Delete all content which orphans the anchor
31109    editor
31110        .update(cx, |editor, window, cx| {
31111            editor.select_all(&SelectAll, window, cx);
31112            editor.insert("completely new content", window, cx);
31113            // The cleanup is called automatically in on_buffer_event when Edited fires
31114        })
31115        .unwrap();
31116
31117    // Verify cleanup happened automatically (not manually triggered)
31118    editor
31119        .update(cx, |editor, _window, _cx| {
31120            // Comment should be removed because its anchor became invalid
31121            // and cleanup was called automatically on buffer edit
31122            assert_eq!(editor.total_review_comment_count(), 0);
31123        })
31124        .unwrap();
31125}
31126
31127#[gpui::test]
31128fn test_comments_stored_for_multiple_hunks(cx: &mut TestAppContext) {
31129    init_test(cx, |_| {});
31130
31131    // This test verifies that comments can be stored for multiple different hunks
31132    // and that hunk_comment_count correctly identifies comments per hunk.
31133    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31134
31135    _ = editor.update(cx, |editor, _window, cx| {
31136        let snapshot = editor.buffer().read(cx).snapshot(cx);
31137
31138        // Create two different hunk keys (simulating two different files)
31139        let anchor = snapshot.anchor_before(Point::new(0, 0));
31140        let key1 = DiffHunkKey {
31141            file_path: Arc::from(util::rel_path::RelPath::unix("file1.rs").unwrap()),
31142            hunk_start_anchor: anchor,
31143        };
31144        let key2 = DiffHunkKey {
31145            file_path: Arc::from(util::rel_path::RelPath::unix("file2.rs").unwrap()),
31146            hunk_start_anchor: anchor,
31147        };
31148
31149        // Add comments to first hunk
31150        editor.add_review_comment(
31151            key1.clone(),
31152            "Comment 1 for file1".to_string(),
31153            anchor..anchor,
31154            cx,
31155        );
31156        editor.add_review_comment(
31157            key1.clone(),
31158            "Comment 2 for file1".to_string(),
31159            anchor..anchor,
31160            cx,
31161        );
31162
31163        // Add comment to second hunk
31164        editor.add_review_comment(
31165            key2.clone(),
31166            "Comment for file2".to_string(),
31167            anchor..anchor,
31168            cx,
31169        );
31170
31171        // Verify total count
31172        assert_eq!(editor.total_review_comment_count(), 3);
31173
31174        // Verify per-hunk counts
31175        let snapshot = editor.buffer().read(cx).snapshot(cx);
31176        assert_eq!(
31177            editor.hunk_comment_count(&key1, &snapshot),
31178            2,
31179            "file1 should have 2 comments"
31180        );
31181        assert_eq!(
31182            editor.hunk_comment_count(&key2, &snapshot),
31183            1,
31184            "file2 should have 1 comment"
31185        );
31186
31187        // Verify comments_for_hunk returns correct comments
31188        let file1_comments = editor.comments_for_hunk(&key1, &snapshot);
31189        assert_eq!(file1_comments.len(), 2);
31190        assert_eq!(file1_comments[0].comment, "Comment 1 for file1");
31191        assert_eq!(file1_comments[1].comment, "Comment 2 for file1");
31192
31193        let file2_comments = editor.comments_for_hunk(&key2, &snapshot);
31194        assert_eq!(file2_comments.len(), 1);
31195        assert_eq!(file2_comments[0].comment, "Comment for file2");
31196    });
31197}
31198
31199#[gpui::test]
31200fn test_same_hunk_detected_by_matching_keys(cx: &mut TestAppContext) {
31201    init_test(cx, |_| {});
31202
31203    // This test verifies that hunk_keys_match correctly identifies when two
31204    // DiffHunkKeys refer to the same hunk (same file path and anchor point).
31205    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31206
31207    _ = editor.update(cx, |editor, _window, cx| {
31208        let snapshot = editor.buffer().read(cx).snapshot(cx);
31209        let anchor = snapshot.anchor_before(Point::new(0, 0));
31210
31211        // Create two keys with the same file path and anchor
31212        let key1 = DiffHunkKey {
31213            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
31214            hunk_start_anchor: anchor,
31215        };
31216        let key2 = DiffHunkKey {
31217            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
31218            hunk_start_anchor: anchor,
31219        };
31220
31221        // Add comment to first key
31222        editor.add_review_comment(key1, "Test comment".to_string(), anchor..anchor, cx);
31223
31224        // Verify second key (same hunk) finds the comment
31225        let snapshot = editor.buffer().read(cx).snapshot(cx);
31226        assert_eq!(
31227            editor.hunk_comment_count(&key2, &snapshot),
31228            1,
31229            "Same hunk should find the comment"
31230        );
31231
31232        // Create a key with different file path
31233        let different_file_key = DiffHunkKey {
31234            file_path: Arc::from(util::rel_path::RelPath::unix("other.rs").unwrap()),
31235            hunk_start_anchor: anchor,
31236        };
31237
31238        // Different file should not find the comment
31239        assert_eq!(
31240            editor.hunk_comment_count(&different_file_key, &snapshot),
31241            0,
31242            "Different file should not find the comment"
31243        );
31244    });
31245}
31246
31247#[gpui::test]
31248fn test_overlay_comments_expanded_state(cx: &mut TestAppContext) {
31249    init_test(cx, |_| {});
31250
31251    // This test verifies that set_diff_review_comments_expanded correctly
31252    // updates the expanded state of overlays.
31253    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31254
31255    // Show overlay
31256    editor
31257        .update(cx, |editor, window, cx| {
31258            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
31259        })
31260        .unwrap();
31261
31262    // Verify initially expanded (default)
31263    editor
31264        .update(cx, |editor, _window, _cx| {
31265            assert!(
31266                editor.diff_review_overlays[0].comments_expanded,
31267                "Should be expanded by default"
31268            );
31269        })
31270        .unwrap();
31271
31272    // Set to collapsed using the public method
31273    editor
31274        .update(cx, |editor, _window, cx| {
31275            editor.set_diff_review_comments_expanded(false, cx);
31276        })
31277        .unwrap();
31278
31279    // Verify collapsed
31280    editor
31281        .update(cx, |editor, _window, _cx| {
31282            assert!(
31283                !editor.diff_review_overlays[0].comments_expanded,
31284                "Should be collapsed after setting to false"
31285            );
31286        })
31287        .unwrap();
31288
31289    // Set back to expanded
31290    editor
31291        .update(cx, |editor, _window, cx| {
31292            editor.set_diff_review_comments_expanded(true, cx);
31293        })
31294        .unwrap();
31295
31296    // Verify expanded again
31297    editor
31298        .update(cx, |editor, _window, _cx| {
31299            assert!(
31300                editor.diff_review_overlays[0].comments_expanded,
31301                "Should be expanded after setting to true"
31302            );
31303        })
31304        .unwrap();
31305}
31306
31307#[gpui::test]
31308fn test_diff_review_multiline_selection(cx: &mut TestAppContext) {
31309    init_test(cx, |_| {});
31310
31311    // Create an editor with multiple lines of text
31312    let editor = cx.add_window(|window, cx| {
31313        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\nline 4\nline 5\n", cx));
31314        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31315        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31316    });
31317
31318    // Test showing overlay with a multi-line selection (lines 1-3, which are rows 0-2)
31319    editor
31320        .update(cx, |editor, window, cx| {
31321            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(2), window, cx);
31322        })
31323        .unwrap();
31324
31325    // Verify line range
31326    editor
31327        .update(cx, |editor, _window, cx| {
31328            assert!(!editor.diff_review_overlays.is_empty());
31329            assert_eq!(editor.diff_review_line_range(cx), Some((0, 2)));
31330        })
31331        .unwrap();
31332
31333    // Dismiss and test with reversed range (end < start)
31334    editor
31335        .update(cx, |editor, _window, cx| {
31336            editor.dismiss_all_diff_review_overlays(cx);
31337        })
31338        .unwrap();
31339
31340    // Show overlay with reversed range - should normalize it
31341    editor
31342        .update(cx, |editor, window, cx| {
31343            editor.show_diff_review_overlay(DisplayRow(3)..DisplayRow(1), window, cx);
31344        })
31345        .unwrap();
31346
31347    // Verify range is normalized (start <= end)
31348    editor
31349        .update(cx, |editor, _window, cx| {
31350            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
31351        })
31352        .unwrap();
31353}
31354
31355#[gpui::test]
31356fn test_diff_review_drag_state(cx: &mut TestAppContext) {
31357    init_test(cx, |_| {});
31358
31359    let editor = cx.add_window(|window, cx| {
31360        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
31361        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31362        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31363    });
31364
31365    // Initially no drag state
31366    editor
31367        .update(cx, |editor, _window, _cx| {
31368            assert!(editor.diff_review_drag_state.is_none());
31369        })
31370        .unwrap();
31371
31372    // Start drag at row 1
31373    editor
31374        .update(cx, |editor, window, cx| {
31375            editor.start_diff_review_drag(DisplayRow(1), window, cx);
31376        })
31377        .unwrap();
31378
31379    // Verify drag state is set
31380    editor
31381        .update(cx, |editor, window, cx| {
31382            assert!(editor.diff_review_drag_state.is_some());
31383            let snapshot = editor.snapshot(window, cx);
31384            let range = editor
31385                .diff_review_drag_state
31386                .as_ref()
31387                .unwrap()
31388                .row_range(&snapshot.display_snapshot);
31389            assert_eq!(*range.start(), DisplayRow(1));
31390            assert_eq!(*range.end(), DisplayRow(1));
31391        })
31392        .unwrap();
31393
31394    // Update drag to row 3
31395    editor
31396        .update(cx, |editor, window, cx| {
31397            editor.update_diff_review_drag(DisplayRow(3), window, cx);
31398        })
31399        .unwrap();
31400
31401    // Verify drag state is updated
31402    editor
31403        .update(cx, |editor, window, cx| {
31404            assert!(editor.diff_review_drag_state.is_some());
31405            let snapshot = editor.snapshot(window, cx);
31406            let range = editor
31407                .diff_review_drag_state
31408                .as_ref()
31409                .unwrap()
31410                .row_range(&snapshot.display_snapshot);
31411            assert_eq!(*range.start(), DisplayRow(1));
31412            assert_eq!(*range.end(), DisplayRow(3));
31413        })
31414        .unwrap();
31415
31416    // End drag - should show overlay
31417    editor
31418        .update(cx, |editor, window, cx| {
31419            editor.end_diff_review_drag(window, cx);
31420        })
31421        .unwrap();
31422
31423    // Verify drag state is cleared and overlay is shown
31424    editor
31425        .update(cx, |editor, _window, cx| {
31426            assert!(editor.diff_review_drag_state.is_none());
31427            assert!(!editor.diff_review_overlays.is_empty());
31428            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
31429        })
31430        .unwrap();
31431}
31432
31433#[gpui::test]
31434fn test_diff_review_drag_cancel(cx: &mut TestAppContext) {
31435    init_test(cx, |_| {});
31436
31437    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31438
31439    // Start drag
31440    editor
31441        .update(cx, |editor, window, cx| {
31442            editor.start_diff_review_drag(DisplayRow(0), window, cx);
31443        })
31444        .unwrap();
31445
31446    // Verify drag state is set
31447    editor
31448        .update(cx, |editor, _window, _cx| {
31449            assert!(editor.diff_review_drag_state.is_some());
31450        })
31451        .unwrap();
31452
31453    // Cancel drag
31454    editor
31455        .update(cx, |editor, _window, cx| {
31456            editor.cancel_diff_review_drag(cx);
31457        })
31458        .unwrap();
31459
31460    // Verify drag state is cleared and no overlay was created
31461    editor
31462        .update(cx, |editor, _window, _cx| {
31463            assert!(editor.diff_review_drag_state.is_none());
31464            assert!(editor.diff_review_overlays.is_empty());
31465        })
31466        .unwrap();
31467}
31468
31469#[gpui::test]
31470fn test_calculate_overlay_height(cx: &mut TestAppContext) {
31471    init_test(cx, |_| {});
31472
31473    // This test verifies that calculate_overlay_height returns correct heights
31474    // based on comment count and expanded state.
31475    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31476
31477    _ = editor.update(cx, |editor, _window, cx| {
31478        let snapshot = editor.buffer().read(cx).snapshot(cx);
31479        let anchor = snapshot.anchor_before(Point::new(0, 0));
31480        let key = DiffHunkKey {
31481            file_path: Arc::from(util::rel_path::RelPath::empty()),
31482            hunk_start_anchor: anchor,
31483        };
31484
31485        // No comments: base height of 2
31486        let height_no_comments = editor.calculate_overlay_height(&key, true, &snapshot);
31487        assert_eq!(
31488            height_no_comments, 2,
31489            "Base height should be 2 with no comments"
31490        );
31491
31492        // Add one comment
31493        editor.add_review_comment(key.clone(), "Comment 1".to_string(), anchor..anchor, cx);
31494
31495        let snapshot = editor.buffer().read(cx).snapshot(cx);
31496
31497        // With comments expanded: base (2) + header (1) + 2 per comment
31498        let height_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
31499        assert_eq!(
31500            height_expanded,
31501            2 + 1 + 2, // base + header + 1 comment * 2
31502            "Height with 1 comment expanded"
31503        );
31504
31505        // With comments collapsed: base (2) + header (1)
31506        let height_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
31507        assert_eq!(
31508            height_collapsed,
31509            2 + 1, // base + header only
31510            "Height with comments collapsed"
31511        );
31512
31513        // Add more comments
31514        editor.add_review_comment(key.clone(), "Comment 2".to_string(), anchor..anchor, cx);
31515        editor.add_review_comment(key.clone(), "Comment 3".to_string(), anchor..anchor, cx);
31516
31517        let snapshot = editor.buffer().read(cx).snapshot(cx);
31518
31519        // With 3 comments expanded
31520        let height_3_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
31521        assert_eq!(
31522            height_3_expanded,
31523            2 + 1 + (3 * 2), // base + header + 3 comments * 2
31524            "Height with 3 comments expanded"
31525        );
31526
31527        // Collapsed height stays the same regardless of comment count
31528        let height_3_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
31529        assert_eq!(
31530            height_3_collapsed,
31531            2 + 1, // base + header only
31532            "Height with 3 comments collapsed should be same as 1 comment collapsed"
31533        );
31534    });
31535}
31536
31537#[gpui::test]
31538async fn test_move_to_start_end_of_larger_syntax_node_single_cursor(cx: &mut TestAppContext) {
31539    init_test(cx, |_| {});
31540
31541    let language = Arc::new(Language::new(
31542        LanguageConfig::default(),
31543        Some(tree_sitter_rust::LANGUAGE.into()),
31544    ));
31545
31546    let text = r#"
31547        fn main() {
31548            let x = foo(1, 2);
31549        }
31550    "#
31551    .unindent();
31552
31553    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31554    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31555    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31556
31557    editor
31558        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31559        .await;
31560
31561    // Test case 1: Move to end of syntax nodes
31562    editor.update_in(cx, |editor, window, cx| {
31563        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31564            s.select_display_ranges([
31565                DisplayPoint::new(DisplayRow(1), 16)..DisplayPoint::new(DisplayRow(1), 16)
31566            ]);
31567        });
31568    });
31569    editor.update(cx, |editor, cx| {
31570        assert_text_with_selections(
31571            editor,
31572            indoc! {r#"
31573                fn main() {
31574                    let x = foo(ˇ1, 2);
31575                }
31576            "#},
31577            cx,
31578        );
31579    });
31580    editor.update_in(cx, |editor, window, cx| {
31581        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31582    });
31583    editor.update(cx, |editor, cx| {
31584        assert_text_with_selections(
31585            editor,
31586            indoc! {r#"
31587                fn main() {
31588                    let x = foo(1ˇ, 2);
31589                }
31590            "#},
31591            cx,
31592        );
31593    });
31594    editor.update_in(cx, |editor, window, cx| {
31595        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31596    });
31597    editor.update(cx, |editor, cx| {
31598        assert_text_with_selections(
31599            editor,
31600            indoc! {r#"
31601                fn main() {
31602                    let x = foo(1, 2)ˇ;
31603                }
31604            "#},
31605            cx,
31606        );
31607    });
31608    editor.update_in(cx, |editor, window, cx| {
31609        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31610    });
31611    editor.update(cx, |editor, cx| {
31612        assert_text_with_selections(
31613            editor,
31614            indoc! {r#"
31615                fn main() {
31616                    let x = foo(1, 2);ˇ
31617                }
31618            "#},
31619            cx,
31620        );
31621    });
31622    editor.update_in(cx, |editor, window, cx| {
31623        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31624    });
31625    editor.update(cx, |editor, cx| {
31626        assert_text_with_selections(
31627            editor,
31628            indoc! {r#"
31629                fn main() {
31630                    let x = foo(1, 2);
3163131632            "#},
31633            cx,
31634        );
31635    });
31636
31637    // Test case 2: Move to start of syntax nodes
31638    editor.update_in(cx, |editor, window, cx| {
31639        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31640            s.select_display_ranges([
31641                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20)
31642            ]);
31643        });
31644    });
31645    editor.update(cx, |editor, cx| {
31646        assert_text_with_selections(
31647            editor,
31648            indoc! {r#"
31649                fn main() {
31650                    let x = foo(1, 2ˇ);
31651                }
31652            "#},
31653            cx,
31654        );
31655    });
31656    editor.update_in(cx, |editor, window, cx| {
31657        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31658    });
31659    editor.update(cx, |editor, cx| {
31660        assert_text_with_selections(
31661            editor,
31662            indoc! {r#"
31663                fn main() {
31664                    let x = fooˇ(1, 2);
31665                }
31666            "#},
31667            cx,
31668        );
31669    });
31670    editor.update_in(cx, |editor, window, cx| {
31671        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31672    });
31673    editor.update(cx, |editor, cx| {
31674        assert_text_with_selections(
31675            editor,
31676            indoc! {r#"
31677                fn main() {
31678                    let x = ˇfoo(1, 2);
31679                }
31680            "#},
31681            cx,
31682        );
31683    });
31684    editor.update_in(cx, |editor, window, cx| {
31685        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31686    });
31687    editor.update(cx, |editor, cx| {
31688        assert_text_with_selections(
31689            editor,
31690            indoc! {r#"
31691                fn main() {
31692                    ˇlet x = foo(1, 2);
31693                }
31694            "#},
31695            cx,
31696        );
31697    });
31698    editor.update_in(cx, |editor, window, cx| {
31699        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31700    });
31701    editor.update(cx, |editor, cx| {
31702        assert_text_with_selections(
31703            editor,
31704            indoc! {r#"
31705                fn main() ˇ{
31706                    let x = foo(1, 2);
31707                }
31708            "#},
31709            cx,
31710        );
31711    });
31712    editor.update_in(cx, |editor, window, cx| {
31713        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31714    });
31715    editor.update(cx, |editor, cx| {
31716        assert_text_with_selections(
31717            editor,
31718            indoc! {r#"
31719                ˇfn main() {
31720                    let x = foo(1, 2);
31721                }
31722            "#},
31723            cx,
31724        );
31725    });
31726}
31727
31728#[gpui::test]
31729async fn test_move_to_start_end_of_larger_syntax_node_two_cursors(cx: &mut TestAppContext) {
31730    init_test(cx, |_| {});
31731
31732    let language = Arc::new(Language::new(
31733        LanguageConfig::default(),
31734        Some(tree_sitter_rust::LANGUAGE.into()),
31735    ));
31736
31737    let text = r#"
31738        fn main() {
31739            let x = foo(1, 2);
31740            let y = bar(3, 4);
31741        }
31742    "#
31743    .unindent();
31744
31745    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31746    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31747    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31748
31749    editor
31750        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31751        .await;
31752
31753    // Test case 1: Move to end of syntax nodes with two cursors
31754    editor.update_in(cx, |editor, window, cx| {
31755        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31756            s.select_display_ranges([
31757                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20),
31758                DisplayPoint::new(DisplayRow(2), 20)..DisplayPoint::new(DisplayRow(2), 20),
31759            ]);
31760        });
31761    });
31762    editor.update(cx, |editor, cx| {
31763        assert_text_with_selections(
31764            editor,
31765            indoc! {r#"
31766                fn main() {
31767                    let x = foo(1, 2ˇ);
31768                    let y = bar(3, 4ˇ);
31769                }
31770            "#},
31771            cx,
31772        );
31773    });
31774    editor.update_in(cx, |editor, window, cx| {
31775        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31776    });
31777    editor.update(cx, |editor, cx| {
31778        assert_text_with_selections(
31779            editor,
31780            indoc! {r#"
31781                fn main() {
31782                    let x = foo(1, 2)ˇ;
31783                    let y = bar(3, 4)ˇ;
31784                }
31785            "#},
31786            cx,
31787        );
31788    });
31789    editor.update_in(cx, |editor, window, cx| {
31790        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31791    });
31792    editor.update(cx, |editor, cx| {
31793        assert_text_with_selections(
31794            editor,
31795            indoc! {r#"
31796                fn main() {
31797                    let x = foo(1, 2);ˇ
31798                    let y = bar(3, 4);ˇ
31799                }
31800            "#},
31801            cx,
31802        );
31803    });
31804
31805    // Test case 2: Move to start of syntax nodes with two cursors
31806    editor.update_in(cx, |editor, window, cx| {
31807        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31808            s.select_display_ranges([
31809                DisplayPoint::new(DisplayRow(1), 19)..DisplayPoint::new(DisplayRow(1), 19),
31810                DisplayPoint::new(DisplayRow(2), 19)..DisplayPoint::new(DisplayRow(2), 19),
31811            ]);
31812        });
31813    });
31814    editor.update(cx, |editor, cx| {
31815        assert_text_with_selections(
31816            editor,
31817            indoc! {r#"
31818                fn main() {
31819                    let x = foo(1, ˇ2);
31820                    let y = bar(3, ˇ4);
31821                }
31822            "#},
31823            cx,
31824        );
31825    });
31826    editor.update_in(cx, |editor, window, cx| {
31827        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31828    });
31829    editor.update(cx, |editor, cx| {
31830        assert_text_with_selections(
31831            editor,
31832            indoc! {r#"
31833                fn main() {
31834                    let x = fooˇ(1, 2);
31835                    let y = barˇ(3, 4);
31836                }
31837            "#},
31838            cx,
31839        );
31840    });
31841    editor.update_in(cx, |editor, window, cx| {
31842        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31843    });
31844    editor.update(cx, |editor, cx| {
31845        assert_text_with_selections(
31846            editor,
31847            indoc! {r#"
31848                fn main() {
31849                    let x = ˇfoo(1, 2);
31850                    let y = ˇbar(3, 4);
31851                }
31852            "#},
31853            cx,
31854        );
31855    });
31856    editor.update_in(cx, |editor, window, cx| {
31857        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31858    });
31859    editor.update(cx, |editor, cx| {
31860        assert_text_with_selections(
31861            editor,
31862            indoc! {r#"
31863                fn main() {
31864                    ˇlet x = foo(1, 2);
31865                    ˇlet y = bar(3, 4);
31866                }
31867            "#},
31868            cx,
31869        );
31870    });
31871}
31872
31873#[gpui::test]
31874async fn test_move_to_start_end_of_larger_syntax_node_with_selections_and_strings(
31875    cx: &mut TestAppContext,
31876) {
31877    init_test(cx, |_| {});
31878
31879    let language = Arc::new(Language::new(
31880        LanguageConfig::default(),
31881        Some(tree_sitter_rust::LANGUAGE.into()),
31882    ));
31883
31884    let text = r#"
31885        fn main() {
31886            let x = foo(1, 2);
31887            let msg = "hello world";
31888        }
31889    "#
31890    .unindent();
31891
31892    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31893    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31894    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31895
31896    editor
31897        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31898        .await;
31899
31900    // Test case 1: With existing selection, move_to_end keeps selection
31901    editor.update_in(cx, |editor, window, cx| {
31902        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31903            s.select_display_ranges([
31904                DisplayPoint::new(DisplayRow(1), 12)..DisplayPoint::new(DisplayRow(1), 21)
31905            ]);
31906        });
31907    });
31908    editor.update(cx, |editor, cx| {
31909        assert_text_with_selections(
31910            editor,
31911            indoc! {r#"
31912                fn main() {
31913                    let x = «foo(1, 2)ˇ»;
31914                    let msg = "hello world";
31915                }
31916            "#},
31917            cx,
31918        );
31919    });
31920    editor.update_in(cx, |editor, window, cx| {
31921        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31922    });
31923    editor.update(cx, |editor, cx| {
31924        assert_text_with_selections(
31925            editor,
31926            indoc! {r#"
31927                fn main() {
31928                    let x = «foo(1, 2)ˇ»;
31929                    let msg = "hello world";
31930                }
31931            "#},
31932            cx,
31933        );
31934    });
31935
31936    // Test case 2: Move to end within a string
31937    editor.update_in(cx, |editor, window, cx| {
31938        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31939            s.select_display_ranges([
31940                DisplayPoint::new(DisplayRow(2), 15)..DisplayPoint::new(DisplayRow(2), 15)
31941            ]);
31942        });
31943    });
31944    editor.update(cx, |editor, cx| {
31945        assert_text_with_selections(
31946            editor,
31947            indoc! {r#"
31948                fn main() {
31949                    let x = foo(1, 2);
31950                    let msg = "ˇhello world";
31951                }
31952            "#},
31953            cx,
31954        );
31955    });
31956    editor.update_in(cx, |editor, window, cx| {
31957        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31958    });
31959    editor.update(cx, |editor, cx| {
31960        assert_text_with_selections(
31961            editor,
31962            indoc! {r#"
31963                fn main() {
31964                    let x = foo(1, 2);
31965                    let msg = "hello worldˇ";
31966                }
31967            "#},
31968            cx,
31969        );
31970    });
31971
31972    // Test case 3: Move to start within a string
31973    editor.update_in(cx, |editor, window, cx| {
31974        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31975            s.select_display_ranges([
31976                DisplayPoint::new(DisplayRow(2), 21)..DisplayPoint::new(DisplayRow(2), 21)
31977            ]);
31978        });
31979    });
31980    editor.update(cx, |editor, cx| {
31981        assert_text_with_selections(
31982            editor,
31983            indoc! {r#"
31984                fn main() {
31985                    let x = foo(1, 2);
31986                    let msg = "hello ˇworld";
31987                }
31988            "#},
31989            cx,
31990        );
31991    });
31992    editor.update_in(cx, |editor, window, cx| {
31993        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31994    });
31995    editor.update(cx, |editor, cx| {
31996        assert_text_with_selections(
31997            editor,
31998            indoc! {r#"
31999                fn main() {
32000                    let x = foo(1, 2);
32001                    let msg = "ˇhello world";
32002                }
32003            "#},
32004            cx,
32005        );
32006    });
32007}
32008
32009#[gpui::test]
32010async fn test_select_to_start_end_of_larger_syntax_node(cx: &mut TestAppContext) {
32011    init_test(cx, |_| {});
32012
32013    let language = Arc::new(Language::new(
32014        LanguageConfig::default(),
32015        Some(tree_sitter_rust::LANGUAGE.into()),
32016    ));
32017
32018    // Test Group 1.1: Cursor in String - First Jump (Select to End)
32019    let text = r#"let msg = "foo bar baz";"#.unindent();
32020
32021    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32022    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32023    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32024
32025    editor
32026        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32027        .await;
32028
32029    editor.update_in(cx, |editor, window, cx| {
32030        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32031            s.select_display_ranges([
32032                DisplayPoint::new(DisplayRow(0), 14)..DisplayPoint::new(DisplayRow(0), 14)
32033            ]);
32034        });
32035    });
32036    editor.update(cx, |editor, cx| {
32037        assert_text_with_selections(editor, indoc! {r#"let msg = "fooˇ bar baz";"#}, cx);
32038    });
32039    editor.update_in(cx, |editor, window, cx| {
32040        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32041    });
32042    editor.update(cx, |editor, cx| {
32043        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar bazˇ»";"#}, cx);
32044    });
32045
32046    // Test Group 1.2: Cursor in String - Second Jump (Select to End)
32047    editor.update_in(cx, |editor, window, cx| {
32048        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32049    });
32050    editor.update(cx, |editor, cx| {
32051        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz"ˇ»;"#}, cx);
32052    });
32053
32054    // Test Group 1.3: Cursor in String - Third Jump (Select to End)
32055    editor.update_in(cx, |editor, window, cx| {
32056        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32057    });
32058    editor.update(cx, |editor, cx| {
32059        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz";ˇ»"#}, cx);
32060    });
32061
32062    // Test Group 1.4: Cursor in String - First Jump (Select to Start)
32063    editor.update_in(cx, |editor, window, cx| {
32064        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32065            s.select_display_ranges([
32066                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18)
32067            ]);
32068        });
32069    });
32070    editor.update(cx, |editor, cx| {
32071        assert_text_with_selections(editor, indoc! {r#"let msg = "foo barˇ baz";"#}, cx);
32072    });
32073    editor.update_in(cx, |editor, window, cx| {
32074        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32075    });
32076    editor.update(cx, |editor, cx| {
32077        assert_text_with_selections(editor, indoc! {r#"let msg = "«ˇfoo bar» baz";"#}, cx);
32078    });
32079
32080    // Test Group 1.5: Cursor in String - Second Jump (Select to Start)
32081    editor.update_in(cx, |editor, window, cx| {
32082        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32083    });
32084    editor.update(cx, |editor, cx| {
32085        assert_text_with_selections(editor, indoc! {r#"let msg = «ˇ"foo bar» baz";"#}, cx);
32086    });
32087
32088    // Test Group 1.6: Cursor in String - Third Jump (Select to Start)
32089    editor.update_in(cx, |editor, window, cx| {
32090        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32091    });
32092    editor.update(cx, |editor, cx| {
32093        assert_text_with_selections(editor, indoc! {r#"«ˇlet msg = "foo bar» baz";"#}, cx);
32094    });
32095
32096    // Test Group 2.1: Let Statement Progression (Select to End)
32097    let text = r#"
32098fn main() {
32099    let x = "hello";
32100}
32101"#
32102    .unindent();
32103
32104    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32105    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32106    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32107
32108    editor
32109        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32110        .await;
32111
32112    editor.update_in(cx, |editor, window, cx| {
32113        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32114            s.select_display_ranges([
32115                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)
32116            ]);
32117        });
32118    });
32119    editor.update(cx, |editor, cx| {
32120        assert_text_with_selections(
32121            editor,
32122            indoc! {r#"
32123                fn main() {
32124                    let xˇ = "hello";
32125                }
32126            "#},
32127            cx,
32128        );
32129    });
32130    editor.update_in(cx, |editor, window, cx| {
32131        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32132    });
32133    editor.update(cx, |editor, cx| {
32134        assert_text_with_selections(
32135            editor,
32136            indoc! {r##"
32137                fn main() {
32138                    let x« = "hello";ˇ»
32139                }
32140            "##},
32141            cx,
32142        );
32143    });
32144    editor.update_in(cx, |editor, window, cx| {
32145        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32146    });
32147    editor.update(cx, |editor, cx| {
32148        assert_text_with_selections(
32149            editor,
32150            indoc! {r#"
32151                fn main() {
32152                    let x« = "hello";
32153                }ˇ»
32154            "#},
32155            cx,
32156        );
32157    });
32158
32159    // Test Group 2.2a: From Inside String Content Node To String Content Boundary
32160    let text = r#"let x = "hello";"#.unindent();
32161
32162    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32163    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32164    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32165
32166    editor
32167        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32168        .await;
32169
32170    editor.update_in(cx, |editor, window, cx| {
32171        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32172            s.select_display_ranges([
32173                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12)
32174            ]);
32175        });
32176    });
32177    editor.update(cx, |editor, cx| {
32178        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo";"#}, cx);
32179    });
32180    editor.update_in(cx, |editor, window, cx| {
32181        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32182    });
32183    editor.update(cx, |editor, cx| {
32184        assert_text_with_selections(editor, indoc! {r#"let x = "«ˇhel»lo";"#}, cx);
32185    });
32186
32187    // Test Group 2.2b: From Edge of String Content Node To String Literal Boundary
32188    editor.update_in(cx, |editor, window, cx| {
32189        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32190            s.select_display_ranges([
32191                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
32192            ]);
32193        });
32194    });
32195    editor.update(cx, |editor, cx| {
32196        assert_text_with_selections(editor, indoc! {r#"let x = "ˇhello";"#}, cx);
32197    });
32198    editor.update_in(cx, |editor, window, cx| {
32199        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32200    });
32201    editor.update(cx, |editor, cx| {
32202        assert_text_with_selections(editor, indoc! {r#"let x = «ˇ"»hello";"#}, cx);
32203    });
32204
32205    // Test Group 3.1: Create Selection from Cursor (Select to End)
32206    let text = r#"let x = "hello world";"#.unindent();
32207
32208    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32209    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32210    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32211
32212    editor
32213        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32214        .await;
32215
32216    editor.update_in(cx, |editor, window, cx| {
32217        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32218            s.select_display_ranges([
32219                DisplayPoint::new(DisplayRow(0), 14)..DisplayPoint::new(DisplayRow(0), 14)
32220            ]);
32221        });
32222    });
32223    editor.update(cx, |editor, cx| {
32224        assert_text_with_selections(editor, indoc! {r#"let x = "helloˇ world";"#}, cx);
32225    });
32226    editor.update_in(cx, |editor, window, cx| {
32227        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32228    });
32229    editor.update(cx, |editor, cx| {
32230        assert_text_with_selections(editor, indoc! {r#"let x = "hello« worldˇ»";"#}, cx);
32231    });
32232
32233    // Test Group 3.2: Extend Existing Selection (Select to End)
32234    editor.update_in(cx, |editor, window, cx| {
32235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32236            s.select_display_ranges([
32237                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 17)
32238            ]);
32239        });
32240    });
32241    editor.update(cx, |editor, cx| {
32242        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo woˇ»rld";"#}, cx);
32243    });
32244    editor.update_in(cx, |editor, window, cx| {
32245        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32246    });
32247    editor.update(cx, |editor, cx| {
32248        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo worldˇ»";"#}, cx);
32249    });
32250
32251    // Test Group 4.1: Multiple Cursors - All Expand to Different Syntax Nodes
32252    let text = r#"let x = "hello"; let y = 42;"#.unindent();
32253
32254    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32255    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32256    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32257
32258    editor
32259        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32260        .await;
32261
32262    editor.update_in(cx, |editor, window, cx| {
32263        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32264            s.select_display_ranges([
32265                // Cursor inside string content
32266                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
32267                // Cursor at let statement semicolon
32268                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18),
32269                // Cursor inside integer literal
32270                DisplayPoint::new(DisplayRow(0), 26)..DisplayPoint::new(DisplayRow(0), 26),
32271            ]);
32272        });
32273    });
32274    editor.update(cx, |editor, cx| {
32275        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo"; lˇet y = 4ˇ2;"#}, cx);
32276    });
32277    editor.update_in(cx, |editor, window, cx| {
32278        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32279    });
32280    editor.update(cx, |editor, cx| {
32281        assert_text_with_selections(editor, indoc! {r#"let x = "hel«loˇ»"; l«et y = 42;ˇ»"#}, cx);
32282    });
32283
32284    // Test Group 4.2: Multiple Cursors on Separate Lines
32285    let text = r#"
32286let x = "hello";
32287let y = 42;
32288"#
32289    .unindent();
32290
32291    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32292    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32293    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32294
32295    editor
32296        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32297        .await;
32298
32299    editor.update_in(cx, |editor, window, cx| {
32300        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32301            s.select_display_ranges([
32302                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
32303                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9),
32304            ]);
32305        });
32306    });
32307
32308    editor.update(cx, |editor, cx| {
32309        assert_text_with_selections(
32310            editor,
32311            indoc! {r#"
32312                let x = "helˇlo";
32313                let y = 4ˇ2;
32314            "#},
32315            cx,
32316        );
32317    });
32318    editor.update_in(cx, |editor, window, cx| {
32319        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32320    });
32321    editor.update(cx, |editor, cx| {
32322        assert_text_with_selections(
32323            editor,
32324            indoc! {r#"
32325                let x = "hel«loˇ»";
32326                let y = 4«2ˇ»;
32327            "#},
32328            cx,
32329        );
32330    });
32331
32332    // Test Group 5.1: Nested Function Calls
32333    let text = r#"let result = foo(bar("arg"));"#.unindent();
32334
32335    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32336    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32337    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32338
32339    editor
32340        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32341        .await;
32342
32343    editor.update_in(cx, |editor, window, cx| {
32344        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32345            s.select_display_ranges([
32346                DisplayPoint::new(DisplayRow(0), 22)..DisplayPoint::new(DisplayRow(0), 22)
32347            ]);
32348        });
32349    });
32350    editor.update(cx, |editor, cx| {
32351        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("ˇarg"));"#}, cx);
32352    });
32353    editor.update_in(cx, |editor, window, cx| {
32354        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32355    });
32356    editor.update(cx, |editor, cx| {
32357        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«argˇ»"));"#}, cx);
32358    });
32359    editor.update_in(cx, |editor, window, cx| {
32360        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32361    });
32362    editor.update(cx, |editor, cx| {
32363        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg"ˇ»));"#}, cx);
32364    });
32365    editor.update_in(cx, |editor, window, cx| {
32366        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32367    });
32368    editor.update(cx, |editor, cx| {
32369        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg")ˇ»);"#}, cx);
32370    });
32371
32372    // Test Group 6.1: Block Comments
32373    let text = r#"let x = /* multi
32374                             line
32375                             comment */;"#
32376        .unindent();
32377
32378    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32379    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32380    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32381
32382    editor
32383        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32384        .await;
32385
32386    editor.update_in(cx, |editor, window, cx| {
32387        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32388            s.select_display_ranges([
32389                DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16)
32390            ]);
32391        });
32392    });
32393    editor.update(cx, |editor, cx| {
32394        assert_text_with_selections(
32395            editor,
32396            indoc! {r#"
32397let x = /* multiˇ
32398line
32399comment */;"#},
32400            cx,
32401        );
32402    });
32403    editor.update_in(cx, |editor, window, cx| {
32404        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32405    });
32406    editor.update(cx, |editor, cx| {
32407        assert_text_with_selections(
32408            editor,
32409            indoc! {r#"
32410let x = /* multi«
32411line
32412comment */ˇ»;"#},
32413            cx,
32414        );
32415    });
32416
32417    // Test Group 6.2: Array/Vector Literals
32418    let text = r#"let arr = [1, 2, 3];"#.unindent();
32419
32420    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32421    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32422    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32423
32424    editor
32425        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32426        .await;
32427
32428    editor.update_in(cx, |editor, window, cx| {
32429        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32430            s.select_display_ranges([
32431                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
32432            ]);
32433        });
32434    });
32435    editor.update(cx, |editor, cx| {
32436        assert_text_with_selections(editor, indoc! {r#"let arr = [ˇ1, 2, 3];"#}, cx);
32437    });
32438    editor.update_in(cx, |editor, window, cx| {
32439        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32440    });
32441    editor.update(cx, |editor, cx| {
32442        assert_text_with_selections(editor, indoc! {r#"let arr = [«1ˇ», 2, 3];"#}, cx);
32443    });
32444    editor.update_in(cx, |editor, window, cx| {
32445        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32446    });
32447    editor.update(cx, |editor, cx| {
32448        assert_text_with_selections(editor, indoc! {r#"let arr = [«1, 2, 3]ˇ»;"#}, cx);
32449    });
32450}